Skip to content

Commit 14c7618

Browse files
cumason123di
andauthored
V1.1.0 dev (#114)
* 100% test-coverage rule added to tox (#109) * version bump Signed-off-by: Curtis Mason <[email protected]> * adding tests for marshaller Signed-off-by: Curtis Mason <[email protected]> * marshaller 100% test-coverage Signed-off-by: Curtis Mason <[email protected]> * bricked some tests Signed-off-by: Curtis Mason <[email protected]> * additional error handling Signed-off-by: Curtis Mason <[email protected]> * 100% test-coverage Signed-off-by: Curtis Mason <[email protected]> * handles empty data and capitalized headers Signed-off-by: Curtis Mason <[email protected]> * 1.1.0 version bump Signed-off-by: Curtis Mason <[email protected]> * Removed _http suffix from http_methods (#108) * Removed _http suffix from http_methods to_binary_http renamed to_binary, and to_structured_http renamed to_structured. These functions are inside of cloudevents.http thus the _http part should be implicitly understood. Signed-off-by: Curtis Mason <[email protected]> * version bump Signed-off-by: Curtis Mason <[email protected]> * deprecated instead of removal Signed-off-by: Curtis Mason <[email protected]> * Update setup.py Co-authored-by: Dustin Ingram <[email protected]> Signed-off-by: Curtis Mason <[email protected]> * 1.1.0 version bump Signed-off-by: Curtis Mason <[email protected]> Co-authored-by: Dustin Ingram <[email protected]> * swapped args for from_http (#110) Signed-off-by: Curtis Mason <[email protected]> * exception names shortened (#111) * exception names shortened Signed-off-by: Curtis Mason <[email protected]> * to_structured documentation Signed-off-by: Curtis Mason <[email protected]> * adjusted readme and changelog (#113) * adjusted readme and changelog Signed-off-by: Curtis Mason <[email protected]> * readme adjustment Signed-off-by: Curtis Mason <[email protected]> * structured content mode Signed-off-by: Curtis Mason <[email protected]> Co-authored-by: Dustin Ingram <[email protected]>
1 parent d95b130 commit 14c7618

32 files changed

+627
-104
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [1.1.0]
8+
### Changed
9+
- Changed from_http to now expect headers argument before data ([#110])
10+
- Renamed exception names ([#111])
11+
12+
### Deprecated
13+
- Renamed to_binary_http and to_structured_http. ([#108])
14+
715
## [1.0.1]
816
### Added
917
- CloudEvent exceptions and event type checking in http module ([#96])
@@ -93,4 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
93101
[#71]: https://github.com/cloudevents/sdk-python/pull/71
94102
[#72]: https://github.com/cloudevents/sdk-python/pull/72
95103
[#96]: https://github.com/cloudevents/sdk-python/pull/96
96-
[#98]: https://github.com/cloudevents/sdk-python/pull/98
104+
[#98]: https://github.com/cloudevents/sdk-python/pull/98
105+
[#108]: https://github.com/cloudevents/sdk-python/pull/108
106+
[#110]: https://github.com/cloudevents/sdk-python/pull/110
107+
[#111]: https://github.com/cloudevents/sdk-python/pull/111

README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ Below we will provide samples on how to send cloudevents using the popular
2424
### Binary HTTP CloudEvent
2525

2626
```python
27-
from cloudevents.http import CloudEvent, to_binary_http
27+
from cloudevents.http import CloudEvent, to_binary
2828
import requests
2929

30-
31-
# This data defines a binary cloudevent
30+
# Create a CloudEvent
31+
# - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0".
3232
attributes = {
3333
"type": "com.example.sampletype1",
3434
"source": "https://example.com/event-producer",
3535
}
3636
data = {"message": "Hello World!"}
37-
3837
event = CloudEvent(attributes, data)
39-
headers, body = to_binary_http(event)
38+
39+
# Creates the HTTP request representation of the CloudEvent in binary content mode
40+
headers, body = to_binary(event)
4041

4142
# POST
4243
requests.post("<some-url>", data=body, headers=headers)
@@ -45,18 +46,20 @@ requests.post("<some-url>", data=body, headers=headers)
4546
### Structured HTTP CloudEvent
4647

4748
```python
48-
from cloudevents.http import CloudEvent, to_structured_http
49+
from cloudevents.http import CloudEvent, to_structured
4950
import requests
5051

51-
52-
# This data defines a structured cloudevent
52+
# Create a CloudEvent
53+
# - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0".
5354
attributes = {
5455
"type": "com.example.sampletype2",
5556
"source": "https://example.com/event-producer",
5657
}
5758
data = {"message": "Hello World!"}
5859
event = CloudEvent(attributes, data)
59-
headers, body = to_structured_http(event)
60+
61+
# Creates the HTTP request representation of the CloudEvent in structured content mode
62+
headers, body = to_structured(event)
6063

6164
# POST
6265
requests.post("<some-url>", data=body, headers=headers)
@@ -81,7 +84,7 @@ app = Flask(__name__)
8184
@app.route("/", methods=["POST"])
8285
def home():
8386
# create a CloudEvent
84-
event = from_http(request.get_data(), request.headers)
87+
event = from_http(request.headers, request.get_data())
8588

8689
# you can access cloudevent fields as seen below
8790
print(

cloudevents/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.1"
1+
__version__ = "1.1.0"

cloudevents/exceptions.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
class CloudEventMissingRequiredFields(Exception):
14+
class MissingRequiredFields(Exception):
1515
pass
1616

1717

18-
class CloudEventTypeErrorRequiredFields(Exception):
18+
class InvalidRequiredFields(Exception):
19+
pass
20+
21+
22+
class InvalidStructuredJSON(Exception):
23+
pass
24+
25+
26+
class InvalidHeadersFormat(Exception):
1927
pass

cloudevents/http/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
from cloudevents.http.event_type import is_binary, is_structured
1919
from cloudevents.http.http_methods import (
2020
from_http,
21+
to_binary,
2122
to_binary_http,
23+
to_structured,
2224
to_structured_http,
2325
)
2426
from cloudevents.http.json_methods import from_json, to_json

cloudevents/http/event.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ def __init__(
5858
).isoformat()
5959

6060
if self._attributes["specversion"] not in _required_by_version:
61-
raise cloud_exceptions.CloudEventMissingRequiredFields(
62-
f"Invalid specversion: {self._attributes['specversion']}"
61+
raise cloud_exceptions.MissingRequiredFields(
62+
f"Invalid specversion: {self._attributes['specversion']}. "
6363
)
6464
# There is no good way to default 'source' and 'type', so this
6565
# checks for those (or any new required attributes).
6666
required_set = _required_by_version[self._attributes["specversion"]]
6767
if not required_set <= self._attributes.keys():
68-
raise cloud_exceptions.CloudEventMissingRequiredFields(
69-
f"Missing required keys: {required_set - attributes.keys()}"
68+
raise cloud_exceptions.MissingRequiredFields(
69+
f"Missing required keys: {required_set - self._attributes.keys()}. "
7070
)
7171

7272
def __eq__(self, other):

cloudevents/http/http_methods.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import typing
33

4+
from deprecation import deprecated
5+
46
import cloudevents.exceptions as cloud_exceptions
57
from cloudevents.http.event import CloudEvent
68
from cloudevents.http.event_type import is_binary, is_structured
@@ -10,20 +12,30 @@
1012

1113

1214
def from_http(
13-
data: typing.Union[str, bytes],
1415
headers: typing.Dict[str, str],
16+
data: typing.Union[str, bytes, None],
1517
data_unmarshaller: types.UnmarshallerType = None,
1618
):
1719
"""
1820
Unwrap a CloudEvent (binary or structured) from an HTTP request.
19-
:param data: the HTTP request body
20-
:type data: typing.IO
2121
:param headers: the HTTP headers
2222
:type headers: typing.Dict[str, str]
23+
:param data: the HTTP request body
24+
:type data: typing.IO
2325
:param data_unmarshaller: Callable function to map data to a python object
2426
e.g. lambda x: x or lambda x: json.loads(x)
2527
:type data_unmarshaller: types.UnmarshallerType
2628
"""
29+
if data is None:
30+
data = ""
31+
32+
if not isinstance(data, (str, bytes, bytearray)):
33+
raise cloud_exceptions.InvalidStructuredJSON(
34+
"Expected json of type (str, bytes, bytearray), "
35+
f"but instead found {type(data)}. "
36+
)
37+
38+
headers = {key.lower(): value for key, value in headers.items()}
2739
if data_unmarshaller is None:
2840
data_unmarshaller = _json_or_string
2941

@@ -32,19 +44,25 @@ def from_http(
3244
if is_binary(headers):
3345
specversion = headers.get("ce-specversion", None)
3446
else:
35-
raw_ce = json.loads(data)
47+
try:
48+
raw_ce = json.loads(data)
49+
except json.decoder.JSONDecodeError:
50+
raise cloud_exceptions.InvalidStructuredJSON(
51+
"Failed to read fields from structured event. "
52+
f"The following can not be parsed as json: {data}. "
53+
)
3654
specversion = raw_ce.get("specversion", None)
3755

3856
if specversion is None:
39-
raise cloud_exceptions.CloudEventMissingRequiredFields(
40-
"could not find specversion in HTTP request"
57+
raise cloud_exceptions.MissingRequiredFields(
58+
"Failed to find specversion in HTTP request. "
4159
)
4260

4361
event_handler = _obj_by_version.get(specversion, None)
4462

4563
if event_handler is None:
46-
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
47-
f"found invalid specversion {specversion}"
64+
raise cloud_exceptions.InvalidRequiredFields(
65+
f"Found invalid specversion {specversion}. "
4866
)
4967

5068
event = marshall.FromRequest(
@@ -77,8 +95,8 @@ def _to_http(
7795
data_marshaller = _marshaller_by_format[format]
7896

7997
if event._attributes["specversion"] not in _obj_by_version:
80-
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
81-
f"Unsupported specversion: {event._attributes['specversion']}"
98+
raise cloud_exceptions.InvalidRequiredFields(
99+
f"Unsupported specversion: {event._attributes['specversion']}. "
82100
)
83101

84102
event_handler = _obj_by_version[event._attributes["specversion"]]()
@@ -91,11 +109,13 @@ def _to_http(
91109
)
92110

93111

94-
def to_structured_http(
112+
def to_structured(
95113
event: CloudEvent, data_marshaller: types.MarshallerType = None,
96114
) -> (dict, typing.Union[bytes, str]):
97115
"""
98-
Returns a tuple of HTTP headers/body dicts representing this cloudevent
116+
Returns a tuple of HTTP headers/body dicts representing this cloudevent. If
117+
event.data is a byte object, body will have a data_base64 field instead of
118+
data.
99119
100120
:param event: CloudEvent to cast into http data
101121
:type event: CloudEvent
@@ -107,7 +127,7 @@ def to_structured_http(
107127
return _to_http(event=event, data_marshaller=data_marshaller)
108128

109129

110-
def to_binary_http(
130+
def to_binary(
111131
event: CloudEvent, data_marshaller: types.MarshallerType = None,
112132
) -> (dict, typing.Union[bytes, str]):
113133
"""
@@ -125,3 +145,17 @@ def to_binary_http(
125145
format=converters.TypeBinary,
126146
data_marshaller=data_marshaller,
127147
)
148+
149+
150+
@deprecated(deprecated_in="1.0.2", details="Use to_binary function instead")
151+
def to_binary_http(
152+
event: CloudEvent, data_marshaller: types.MarshallerType = None,
153+
) -> (dict, typing.Union[bytes, str]):
154+
return to_binary(event, data_marshaller)
155+
156+
157+
@deprecated(deprecated_in="1.0.2", details="Use to_structured function instead")
158+
def to_structured_http(
159+
event: CloudEvent, data_marshaller: types.MarshallerType = None,
160+
) -> (dict, typing.Union[bytes, str]):
161+
return to_structured(event, data_marshaller)

cloudevents/http/json_methods.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typing
22

33
from cloudevents.http.event import CloudEvent
4-
from cloudevents.http.http_methods import from_http, to_structured_http
4+
from cloudevents.http.http_methods import from_http, to_structured
55
from cloudevents.sdk import types
66

77

@@ -17,7 +17,7 @@ def to_json(
1717
:type data_marshaller: typing.Callable
1818
:returns: json object representing the given event
1919
"""
20-
return to_structured_http(event, data_marshaller=data_marshaller)[1]
20+
return to_structured(event, data_marshaller=data_marshaller)[1]
2121

2222

2323
def from_json(
@@ -33,4 +33,4 @@ def from_json(
3333
:type data_unmarshaller: typing.Callable
3434
:returns: CloudEvent representing given cloudevent json object
3535
"""
36-
return from_http(data=data, headers={}, data_unmarshaller=data_unmarshaller)
36+
return from_http(headers={}, data=data, data_unmarshaller=data_unmarshaller)

cloudevents/http/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def default_marshaller(content: any):
1212

1313

1414
def _json_or_string(content: typing.Union[str, bytes]):
15-
if len(content) == 0:
15+
if content is None or len(content) == 0:
1616
return None
1717
try:
1818
return json.loads(content)

cloudevents/sdk/event/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# TODO(slinkydeveloper) is this really needed?
2323

2424

25-
class EventGetterSetter(object):
25+
class EventGetterSetter(object): # pragma: no cover
2626

2727
# ce-specversion
2828
def CloudEventVersion(self) -> str:
@@ -220,7 +220,7 @@ def UnmarshalJSON(
220220

221221
missing_fields = self._ce_required_fields - raw_ce.keys()
222222
if len(missing_fields) > 0:
223-
raise cloud_exceptions.CloudEventMissingRequiredFields(
223+
raise cloud_exceptions.MissingRequiredFields(
224224
f"Missing required attributes: {missing_fields}"
225225
)
226226

@@ -246,7 +246,7 @@ def UnmarshalBinary(
246246
missing_fields = required_binary_fields - headers.keys()
247247

248248
if len(missing_fields) > 0:
249-
raise cloud_exceptions.CloudEventMissingRequiredFields(
249+
raise cloud_exceptions.MissingRequiredFields(
250250
f"Missing required attributes: {missing_fields}"
251251
)
252252

0 commit comments

Comments
 (0)