Skip to content

Commit c9eec27

Browse files
authored
Merge pull request cloudevents#9 from evankanderson/boonary
Updates for binary encoding
2 parents d8cec17 + 47873fb commit c9eec27

8 files changed

+69
-41
lines changed

cloudevents/sdk/converters/base.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def read(self, event, headers: dict, body: typing.IO,
2525
data_unmarshaller: typing.Callable) -> base.BaseEvent:
2626
raise Exception("not implemented")
2727

28-
def event_supported(self, event):
28+
def event_supported(self, event: object) -> bool:
2929
raise Exception("not implemented")
3030

31-
def can_read(self, content_type):
31+
def can_read(self, content_type: str) -> bool:
3232
raise Exception("not implemented")
3333

3434
def write(self, event: base.BaseEvent,

cloudevents/sdk/converters/binary.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ class BinaryHTTPCloudEventConverter(base.Converter):
2525
TYPE = "binary"
2626
SUPPORTED_VERSIONS = [v02.Event, ]
2727

28-
def can_read(self, content_type):
28+
def can_read(self, content_type: str) -> bool:
2929
return True
3030

31-
def event_supported(self, event):
32-
if type(event) not in self.SUPPORTED_VERSIONS:
33-
raise exceptions.UnsupportedEvent(type(event))
31+
def event_supported(self, event: object) -> bool:
32+
return type(event) in self.SUPPORTED_VERSIONS
3433

3534
def read(self,
3635
event: event_base.BaseEvent,

cloudevents/sdk/converters/structured.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
class JSONHTTPCloudEventConverter(base.Converter):
2222

2323
TYPE = "structured"
24+
MIME_TYPE = "application/cloudevents+json"
2425

25-
def can_read(self, content_type):
26-
return content_type == "application/cloudevents+json"
26+
def can_read(self, content_type: str) -> bool:
27+
return content_type and content_type.startswith(self.MIME_TYPE)
2728

28-
def event_supported(self, event):
29+
def event_supported(self, event: object) -> bool:
2930
# structured format supported by both spec 0.1 and 0.2
30-
pass
31+
return True
3132

3233
def read(self, event: event_base.BaseEvent,
3334
headers: dict,
@@ -39,7 +40,8 @@ def read(self, event: event_base.BaseEvent,
3940
def write(self,
4041
event: event_base.BaseEvent,
4142
data_marshaller: typing.Callable) -> (dict, typing.IO):
42-
return {}, event.MarshalJSON(data_marshaller)
43+
http_headers = {'content-type': self.MIME_TYPE}
44+
return http_headers, event.MarshalJSON(data_marshaller)
4345

4446

4547
def NewJSONHTTPCloudEventConverter() -> JSONHTTPCloudEventConverter:

cloudevents/sdk/event/base.py

+18-16
Original file line numberDiff line numberDiff line change
@@ -129,32 +129,34 @@ def UnmarshalJSON(self, b: typing.IO,
129129

130130
def UnmarshalBinary(self, headers: dict, body: typing.IO,
131131
data_unmarshaller: typing.Callable):
132-
props = self.Properties(with_nullable=True)
133-
exts = props.get("extensions")
134-
for key in props:
135-
formatted_key = "ce-{0}".format(key)
136-
if key != "extensions":
137-
self.Set(key, headers.get("ce-{0}".format(key)))
138-
if formatted_key in headers:
139-
del headers[formatted_key]
140-
141-
# rest of headers suppose to an extension?
142-
exts.update(**headers)
143-
self.Set("extensions", exts)
132+
BINARY_MAPPING = {
133+
'content-type': 'contenttype',
134+
# TODO(someone): add Distributed Tracing. It's not clear
135+
# if this is one extension or two.
136+
# https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md
137+
}
138+
for header, value in headers.items():
139+
header = header.lower()
140+
if header in BINARY_MAPPING:
141+
self.Set(BINARY_MAPPING[header], value)
142+
elif header.startswith("ce-"):
143+
self.Set(header[3:], value)
144+
144145
self.Set("data", data_unmarshaller(body))
145146

146147
def MarshalBinary(
147148
self, data_marshaller: typing.Callable) -> (dict, object):
148149
headers = {}
150+
if self.ContentType():
151+
headers["content-type"] = self.ContentType()
149152
props = self.Properties()
150153
for key, value in props.items():
151-
if key not in ["data", "extensions"]:
154+
if key not in ["data", "extensions", "contenttype"]:
152155
if value is not None:
153156
headers["ce-{0}".format(key)] = value
154157

155-
exts = props.get("extensions")
156-
if len(exts) > 0:
157-
headers.update(**exts)
158+
for key, value in props.get("extensions"):
159+
headers["ce-{0}".format(key)] = value
158160

159161
data, _ = self.Get("data")
160162
return headers, io.BytesIO(

cloudevents/sdk/marshaller.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def __init__(self, converters: typing.List[base.Converter]):
3535
:param converters: a list of HTTP-to-CloudEvent-to-HTTP constructors
3636
:type converters: typing.List[base.Converter]
3737
"""
38-
self.__converters = {c.TYPE: c for c in converters}
38+
self.__converters = (c for c in converters)
39+
self.__converters_by_type = {c.TYPE: c for c in converters}
3940

4041
def FromRequest(self, event: event_base.BaseEvent,
4142
headers: dict,
@@ -61,12 +62,13 @@ def FromRequest(self, event: event_base.BaseEvent,
6162
content_type = headers.get(
6263
"content-type", headers.get("Content-Type"))
6364

64-
for _, cnvrtr in self.__converters.items():
65-
if cnvrtr.can_read(content_type):
66-
cnvrtr.event_supported(event)
65+
for cnvrtr in self.__converters:
66+
if cnvrtr.can_read(content_type) and cnvrtr.event_supported(event):
6767
return cnvrtr.read(event, headers, body, data_unmarshaller)
6868

69-
raise exceptions.UnsupportedEventConverter(content_type)
69+
raise exceptions.UnsupportedEventConverter(
70+
"No registered marshaller for {0} in {1}".format(
71+
content_type, self.__converters))
7072

7173
def ToRequest(self, event: event_base.BaseEvent,
7274
converter_type: str,
@@ -85,8 +87,8 @@ def ToRequest(self, event: event_base.BaseEvent,
8587
if not isinstance(data_marshaller, typing.Callable):
8688
raise exceptions.InvalidDataMarshaller()
8789

88-
if converter_type in self.__converters:
89-
cnvrtr = self.__converters.get(converter_type)
90+
if converter_type in self.__converters_by_type:
91+
cnvrtr = self.__converters_by_type[converter_type]
9092
return cnvrtr.write(event, data_marshaller)
9193

9294
raise exceptions.NoSuchConverter(converter_type)

cloudevents/tests/test_event_from_request_converter.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def test_binary_converter_v01():
6666
)
6767

6868
pytest.raises(
69-
exceptions.UnsupportedEvent,
69+
exceptions.UnsupportedEventConverter,
7070
m.FromRequest,
7171
v01.Event, {}, None, lambda x: x)
7272

@@ -102,7 +102,7 @@ def test_structured_converter_v01():
102102
assert event.Get("id") == (data.ce_id, True)
103103

104104

105-
def test_default_http_marshaller():
105+
def test_default_http_marshaller_with_structured():
106106
m = marshaller.NewDefaultHTTPMarshaller()
107107

108108
event = m.FromRequest(
@@ -116,14 +116,29 @@ def test_default_http_marshaller():
116116
assert event.Get("id") == (data.ce_id, True)
117117

118118

119+
def test_default_http_marshaller_with_binary():
120+
m = marshaller.NewDefaultHTTPMarshaller()
121+
122+
event = m.FromRequest(
123+
v02.Event(),
124+
data.headers,
125+
io.StringIO(json.dumps(data.body)),
126+
json.load
127+
)
128+
assert event is not None
129+
assert event.Get("type") == (data.ce_type, True)
130+
assert event.Get("data") == (data.body, True)
131+
assert event.Get("id") == (data.ce_id, True)
132+
133+
119134
def test_unsupported_event_configuration():
120135
m = marshaller.NewHTTPMarshaller(
121136
[
122137
binary.NewBinaryHTTPCloudEventConverter()
123138
]
124139
)
125140
pytest.raises(
126-
exceptions.UnsupportedEvent,
141+
exceptions.UnsupportedEventConverter,
127142
m.FromRequest,
128143
v01.Event(),
129144
{"Content-Type": "application/cloudevents+json"},

cloudevents/tests/test_event_pipeline.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_event_pipeline_upstream():
4343
assert "ce-source" in new_headers
4444
assert "ce-id" in new_headers
4545
assert "ce-time" in new_headers
46-
assert "ce-contenttype" in new_headers
46+
assert "content-type" in new_headers
4747
assert isinstance(body, io.BytesIO)
4848
assert data.body == body.read().decode("utf-8")
4949

@@ -66,7 +66,7 @@ def test_event_pipeline_v01():
6666

6767
_, body = m.ToRequest(event, converters.TypeStructured, lambda x: x)
6868
assert isinstance(body, io.BytesIO)
69-
new_headers = json.load(body)
69+
new_headers = json.load(io.TextIOWrapper(body, encoding='utf-8'))
7070
assert new_headers is not None
7171
assert "cloudEventsVersion" in new_headers
7272
assert "eventType" in new_headers

cloudevents/tests/test_event_to_request_converter.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ def test_binary_event_to_request_upstream():
4848
def test_structured_event_to_request_upstream():
4949
copy_of_ce = copy.deepcopy(data.ce)
5050
m = marshaller.NewDefaultHTTPMarshaller()
51+
http_headers = {"content-type": "application/cloudevents+json"}
5152
event = m.FromRequest(
5253
v02.Event(),
53-
{"Content-Type": "application/cloudevents+json"},
54+
http_headers,
5455
io.StringIO(json.dumps(data.ce)),
5556
lambda x: x.read()
5657
)
@@ -60,6 +61,9 @@ def test_structured_event_to_request_upstream():
6061

6162
new_headers, _ = m.ToRequest(event, converters.TypeStructured, lambda x: x)
6263
for key in new_headers:
64+
if key == "content-type":
65+
assert new_headers[key] == http_headers[key]
66+
continue
6367
assert key in copy_of_ce
6468

6569

@@ -70,9 +74,10 @@ def test_structured_event_to_request_v01():
7074
structured.NewJSONHTTPCloudEventConverter()
7175
]
7276
)
77+
http_headers = {"content-type": "application/cloudevents+json"}
7378
event = m.FromRequest(
7479
v01.Event(),
75-
{"Content-Type": "application/cloudevents+json"},
80+
http_headers,
7681
io.StringIO(json.dumps(data.ce)),
7782
lambda x: x.read()
7883
)
@@ -82,4 +87,7 @@ def test_structured_event_to_request_v01():
8287

8388
new_headers, _ = m.ToRequest(event, converters.TypeStructured, lambda x: x)
8489
for key in new_headers:
90+
if key == "content-type":
91+
assert new_headers[key] == http_headers[key]
92+
continue
8593
assert key in copy_of_ce

0 commit comments

Comments
 (0)