Skip to content

Commit 0d5ac25

Browse files
committed
docstrings, marshallers
Signed-off-by: Denis Makogon <[email protected]>
1 parent 3be2570 commit 0d5ac25

File tree

8 files changed

+118
-37
lines changed

8 files changed

+118
-37
lines changed

cloudevents/sdk/converters/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ def __init__(
3030
def can_read(self, media_type: str) -> bool:
3131
return media_type in self.supported_media_types
3232

33-
def can_write(self, media_type: str) -> bool:
34-
return media_type in self.supported_media_types
35-
36-
def read(self, headers: dict, body: typing.IO) -> base.BaseEvent:
33+
def read(self, headers: dict, body: typing.IO,
34+
data_unmarshaller: typing.Callable) -> base.BaseEvent:
3735
raise Exception("not implemented")
3836

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

cloudevents/sdk/converters/binary.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,18 @@ def __init__(self, event_class: event_base.BaseEvent,
3232
super().__init__(event_class, supported_media_types)
3333

3434
def read(self,
35-
headers: dict, body: typing.IO) -> event_base.BaseEvent:
35+
headers: dict, body: typing.IO,
36+
data_unmarshaller: typing.Callable) -> event_base.BaseEvent:
3637
# we ignore headers, since the whole CE is in request body
3738
event = self.event
38-
event.UnmarshalBinary(headers, body)
39+
event.UnmarshalBinary(headers, body, data_unmarshaller)
3940
return event
4041

4142
def write(self, event: event_base.BaseEvent,
4243
data_marshaller: typing.Callable) -> (dict, typing.IO):
44+
if not isinstance(data_marshaller, typing.Callable):
45+
raise exceptions.InvalidDataMarshaller()
46+
4347
hs, data = event.MarshalBinary()
4448
return hs, data_marshaller(data)
4549

cloudevents/sdk/converters/structured.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import typing
1616

17+
from cloudevents.sdk import exceptions
1718
from cloudevents.sdk.converters import base
1819
from cloudevents.sdk.event import base as event_base
1920

@@ -27,16 +28,20 @@ def __init__(self, event_class: event_base.BaseEvent,
2728
super().__init__(event_class, supported_media_types)
2829

2930
def read(self, headers: dict,
30-
body: typing.IO) -> event_base.BaseEvent:
31+
body: typing.IO,
32+
data_unmarshaller: typing.Callable) -> event_base.BaseEvent:
3133
# we ignore headers, since the whole CE is in request body
3234
event = self.event
33-
event.UnmarshalJSON(body)
35+
event.UnmarshalJSON(body, data_unmarshaller)
3436
return event
3537

3638
def write(self,
3739
event: event_base.BaseEvent,
3840
data_marshaller: typing.Callable) -> (dict, typing.IO):
39-
return {}, event.MarshalJSON()
41+
if not isinstance(data_marshaller, typing.Callable):
42+
raise exceptions.InvalidDataMarshaller()
43+
44+
return {}, event.MarshalJSON(data_marshaller)
4045

4146

4247
def NewJSONHTTPCloudEventConverter(

cloudevents/sdk/event/base.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,21 @@ def Set(self, key: str, value: object):
114114
exts.update({key: value})
115115
self.Set("extensions", exts)
116116

117-
def MarshalJSON(self) -> typing.IO:
118-
return io.StringIO(ujson.dumps(self.Properties()))
117+
def MarshalJSON(self, data_marshaller: typing.Callable) -> typing.IO:
118+
props = self.Properties()
119+
props["data"] = data_marshaller(props.get("data"))
120+
return io.StringIO(ujson.dumps(props))
119121

120-
def UnmarshalJSON(self, b: typing.IO):
122+
def UnmarshalJSON(self, b: typing.IO,
123+
data_unmarshaller: typing.Callable):
121124
raw_ce = ujson.load(b)
122125
for name, value in raw_ce.items():
126+
if name == "data":
127+
value = data_unmarshaller(value)
123128
self.Set(name, value)
124129

125-
def UnmarshalBinary(self, headers: dict, body: typing.IO):
130+
def UnmarshalBinary(self, headers: dict, body: typing.IO,
131+
data_unmarshaller: typing.Callable):
126132
props = self.Properties(with_nullable=True)
127133
exts = props.get("extensions")
128134
for key in props:
@@ -133,15 +139,9 @@ def UnmarshalBinary(self, headers: dict, body: typing.IO):
133139
del headers[formatted_key]
134140

135141
# rest of headers suppose to an extension?
136-
137142
exts.update(**headers)
138143
self.Set("extensions", exts)
139-
140-
data = None
141-
if body:
142-
data = body.read()
143-
144-
self.Set("data", data)
144+
self.Set("data", data_unmarshaller(body))
145145

146146
def MarshalBinary(self) -> (dict, object):
147147
headers = {}

cloudevents/sdk/exceptions.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,29 @@
1313
# under the License.
1414

1515

16-
class InvalidMimeType(Exception):
16+
class InvalidMimeTypeFromRequest(Exception):
1717

1818
def __init__(self, mime_type):
1919
super().__init__(
20-
"Invalid MIME type: {0}".format(mime_type))
20+
"Unable to read CloudEvent from request, "
21+
"invalid MIME type: {0}".format(mime_type))
2122

2223

2324
class UnsupportedEvent(Exception):
2425

2526
def __init__(self, event_class):
2627
super().__init__("Invalid CloudEvent class: "
2728
"'{0}'".format(event_class))
29+
30+
31+
class InvalidDataMarshaller(Exception):
32+
33+
def __init__(self):
34+
super().__init__(
35+
"Invalid data marshaller, is not a callable")
36+
37+
38+
class NoSuchConverter(Exception):
39+
def __init__(self, converter_type):
40+
super().__init__(
41+
"No such converter {0}".format(converter_type))

cloudevents/sdk/marshaller.py

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,72 @@
2424

2525

2626
class HTTPMarshaller(object):
27+
"""
28+
HTTP Marshaller class.
29+
API of this class designed to work with CloudEvent (upstream and v0.1)
30+
"""
2731

2832
def __init__(self, converters: typing.List[base.Converter]):
29-
self.__converters = converters
33+
"""
34+
CloudEvent HTTP marshaller constructor
35+
:param converters: a list of HTTP-to-CloudEvent-to-HTTP constructors
36+
:type converters: typing.List[base.Converter]
37+
"""
38+
self.__converters = {c.TYPE: c for c in converters}
3039

31-
def FromRequest(self, headers: dict, body: typing.IO):
40+
def FromRequest(self, headers: dict,
41+
body: typing.IO,
42+
data_unmarshaller:
43+
typing.Callable) -> event_base.BaseEvent:
44+
"""
45+
Reads a CloudEvent from an HTTP headers and request body
46+
:param headers: a dict-like HTTP headers
47+
:type headers: dict
48+
:param body: a stream-like HTTP request body
49+
:type body: typing.IO
50+
:param data_unmarshaller: a callable-like
51+
unmarshaller the CloudEvent data
52+
:return: a CloudEvent
53+
:rtype: event_base.BaseEvent
54+
"""
3255
mimeType = headers.get("Content-Type")
33-
for cnvrtr in self.__converters:
56+
for _, cnvrtr in self.__converters.items():
3457
if cnvrtr.can_read(mimeType):
35-
return cnvrtr.read(headers, body)
58+
return cnvrtr.read(headers, body, data_unmarshaller)
3659

37-
raise exceptions.InvalidMimeType(mimeType)
60+
raise exceptions.InvalidMimeTypeFromRequest(mimeType)
3861

3962
def ToRequest(self, event: event_base.BaseEvent,
4063
converter_type: str,
4164
data_marshaller: typing.Callable) -> (dict, typing.IO):
42-
for cnvrtv in self.__converters:
43-
if converter_type == cnvrtv.TYPE:
44-
return cnvrtv.write(event, data_marshaller)
65+
"""
66+
Writes a CloudEvent into a HTTP-ready form of headers and request body
67+
:param event: CloudEvent
68+
:type event: event_base.BaseEvent
69+
:param converter_type: a type of CloudEvent-to-HTTP converter
70+
:type converter_type: str
71+
:param data_marshaller: a callable-like marshaller CloudEvent data
72+
:type data_marshaller: typing.Callable
73+
:return: dict of HTTP headers and stream of HTTP request body
74+
:rtype: tuple
75+
"""
76+
if converter_type in self.__converters:
77+
cnvrtr = self.__converters.get(converter_type)
78+
return cnvrtr.write(event, data_marshaller)
79+
80+
raise exceptions.NoSuchConverter(converter_type)
4581

4682

4783
def NewDefaultHTTPMarshaller(
4884
event_class: event_base.BaseEvent) -> HTTPMarshaller:
85+
"""
86+
Creates the default HTTP marshaller with both structured
87+
and binary converters
88+
:param event_class: CloudEvent spec class
89+
:type event_class: event_base.BaseEvent
90+
:return: an instance of HTTP marshaller
91+
:rtype: cloudevents.sdk.marshaller.HTTPMarshaller
92+
"""
4993
return HTTPMarshaller([
5094
structured.NewJSONHTTPCloudEventConverter(event_class),
5195
binary.NewBinaryHTTPCloudEventConverter(event_class),
@@ -54,4 +98,12 @@ def NewDefaultHTTPMarshaller(
5498

5599
def NewHTTPMarshaller(
56100
converters: typing.List[base.Converter]) -> HTTPMarshaller:
101+
"""
102+
Creates the default HTTP marshaller with both
103+
structured and binary converters
104+
:param converters: a list of CloudEvent-to-HTTP-to-CloudEvent converters
105+
:type converters: typing.List[base.Converter]
106+
:return: an instance of HTTP marshaller
107+
:rtype: cloudevents.sdk.marshaller.HTTPMarshaller
108+
"""
57109
return HTTPMarshaller(converters)

cloudevents/tests/test_event_from_request_converter.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_binary_converter_upstream():
3434
binary.NewBinaryHTTPCloudEventConverter(upstream.Event)
3535
]
3636
)
37-
event = m.FromRequest(data.headers, None)
37+
event = m.FromRequest(data.headers, None, lambda x: x)
3838
assert event is not None
3939
assert event.Get("type") == (data.ce_type, True)
4040
assert event.Get("id") == (data.ce_id, True)
@@ -48,7 +48,8 @@ def test_structured_converter_upstream():
4848
)
4949
event = m.FromRequest(
5050
{"Content-Type": "application/cloudevents+json"},
51-
io.StringIO(ujson.dumps(data.ce))
51+
io.StringIO(ujson.dumps(data.ce)),
52+
lambda x: x.read()
5253
)
5354

5455
assert event is not None
@@ -72,7 +73,8 @@ def test_structured_converter_v01():
7273
)
7374
event = m.FromRequest(
7475
{"Content-Type": "application/cloudevents+json"},
75-
io.StringIO(ujson.dumps(data.ce))
76+
io.StringIO(ujson.dumps(data.ce)),
77+
lambda x: x.read()
7678
)
7779

7880
assert event is not None
@@ -85,7 +87,8 @@ def test_default_http_marshaller():
8587

8688
event = m.FromRequest(
8789
{"Content-Type": "application/cloudevents+json"},
88-
io.StringIO(ujson.dumps(data.ce))
90+
io.StringIO(ujson.dumps(data.ce)),
91+
lambda x: x.read()
8992
)
9093
assert event is not None
9194
assert event.Get("type") == (data.ce_type, True)

cloudevents/tests/test_event_to_request_converter.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def test_binary_event_to_request_upstream():
3131
m = marshaller.NewDefaultHTTPMarshaller(upstream.Event)
3232
event = m.FromRequest(
3333
{"Content-Type": "application/cloudevents+json"},
34-
io.StringIO(ujson.dumps(data.ce))
34+
io.StringIO(ujson.dumps(data.ce)),
35+
lambda x: x.read()
3536
)
3637

3738
assert event is not None
@@ -48,7 +49,9 @@ def test_structured_event_to_request_upstream():
4849
m = marshaller.NewDefaultHTTPMarshaller(upstream.Event)
4950
event = m.FromRequest(
5051
{"Content-Type": "application/cloudevents+json"},
51-
io.StringIO(ujson.dumps(data.ce)))
52+
io.StringIO(ujson.dumps(data.ce)),
53+
lambda x: x.read()
54+
)
5255
assert event is not None
5356
assert event.Get("type") == (data.ce_type, True)
5457
assert event.Get("id") == (data.ce_id, True)
@@ -67,7 +70,9 @@ def test_structured_event_to_request_v01():
6770
)
6871
event = m.FromRequest(
6972
{"Content-Type": "application/cloudevents+json"},
70-
io.StringIO(ujson.dumps(data.ce)))
73+
io.StringIO(ujson.dumps(data.ce)),
74+
lambda x: x.read()
75+
)
7176
assert event is not None
7277
assert event.Get("type") == (data.ce_type, True)
7378
assert event.Get("id") == (data.ce_id, True)

0 commit comments

Comments
 (0)