V1.1.0 dev (#114)

* 100% test-coverage rule added to tox (#109)

* version bump

Signed-off-by: Curtis Mason <cumason@google.com>

* adding tests for marshaller

Signed-off-by: Curtis Mason <cumason@google.com>

* marshaller 100% test-coverage

Signed-off-by: Curtis Mason <cumason@bu.edu>

* bricked some tests

Signed-off-by: Curtis Mason <cumason@bu.edu>

* additional error handling

Signed-off-by: Curtis Mason <cumason@bu.edu>

* 100% test-coverage

Signed-off-by: Curtis Mason <cumason@bu.edu>

* handles empty data and capitalized headers

Signed-off-by: Curtis Mason <cumason@bu.edu>

* 1.1.0 version bump

Signed-off-by: Curtis Mason <cumason@bu.edu>

* 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 <cumason@google.com>

* version bump

Signed-off-by: Curtis Mason <cumason@google.com>

* deprecated instead of removal

Signed-off-by: Curtis Mason <cumason@bu.edu>

* Update setup.py

Co-authored-by: Dustin Ingram <di@users.noreply.github.com>
Signed-off-by: Curtis Mason <cumason@bu.edu>

* 1.1.0 version bump

Signed-off-by: Curtis Mason <cumason@bu.edu>

Co-authored-by: Dustin Ingram <di@users.noreply.github.com>

* swapped args for from_http (#110)

Signed-off-by: Curtis Mason <cumason@bu.edu>

* exception names shortened (#111)

* exception names shortened

Signed-off-by: Curtis Mason <cumason@google.com>

* to_structured documentation

Signed-off-by: Curtis Mason <cumason@google.com>

* adjusted readme and changelog (#113)

* adjusted readme and changelog

Signed-off-by: Curtis Mason <cumason@google.com>

* readme adjustment

Signed-off-by: Curtis Mason <cumason@google.com>

* structured content mode

Signed-off-by: Curtis Mason <cumason@google.com>

Co-authored-by: Dustin Ingram <di@users.noreply.github.com>
This commit is contained in:
Curtis Mason 2020-08-18 10:49:02 -04:00 committed by GitHub
parent d95b1303a9
commit 14c76188d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 627 additions and 104 deletions

View File

@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0]
### Changed
- Changed from_http to now expect headers argument before data ([#110])
- Renamed exception names ([#111])
### Deprecated
- Renamed to_binary_http and to_structured_http. ([#108])
## [1.0.1]
### Added
- 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
[#71]: https://github.com/cloudevents/sdk-python/pull/71
[#72]: https://github.com/cloudevents/sdk-python/pull/72
[#96]: https://github.com/cloudevents/sdk-python/pull/96
[#98]: https://github.com/cloudevents/sdk-python/pull/98
[#98]: https://github.com/cloudevents/sdk-python/pull/98
[#108]: https://github.com/cloudevents/sdk-python/pull/108
[#110]: https://github.com/cloudevents/sdk-python/pull/110
[#111]: https://github.com/cloudevents/sdk-python/pull/111

View File

@ -24,19 +24,20 @@ Below we will provide samples on how to send cloudevents using the popular
### Binary HTTP CloudEvent
```python
from cloudevents.http import CloudEvent, to_binary_http
from cloudevents.http import CloudEvent, to_binary
import requests
# This data defines a binary cloudevent
# Create a CloudEvent
# - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0".
attributes = {
"type": "com.example.sampletype1",
"source": "https://example.com/event-producer",
}
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_binary_http(event)
# Creates the HTTP request representation of the CloudEvent in binary content mode
headers, body = to_binary(event)
# POST
requests.post("<some-url>", data=body, headers=headers)
@ -45,18 +46,20 @@ requests.post("<some-url>", data=body, headers=headers)
### Structured HTTP CloudEvent
```python
from cloudevents.http import CloudEvent, to_structured_http
from cloudevents.http import CloudEvent, to_structured
import requests
# This data defines a structured cloudevent
# Create a CloudEvent
# - The CloudEvent "id" is generated if omitted. "specversion" defaults to "1.0".
attributes = {
"type": "com.example.sampletype2",
"source": "https://example.com/event-producer",
}
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_structured_http(event)
# Creates the HTTP request representation of the CloudEvent in structured content mode
headers, body = to_structured(event)
# POST
requests.post("<some-url>", data=body, headers=headers)
@ -81,7 +84,7 @@ app = Flask(__name__)
@app.route("/", methods=["POST"])
def home():
# create a CloudEvent
event = from_http(request.get_data(), request.headers)
event = from_http(request.headers, request.get_data())
# you can access cloudevent fields as seen below
print(

View File

@ -1 +1 @@
__version__ = "1.0.1"
__version__ = "1.1.0"

View File

@ -11,9 +11,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class CloudEventMissingRequiredFields(Exception):
class MissingRequiredFields(Exception):
pass
class CloudEventTypeErrorRequiredFields(Exception):
class InvalidRequiredFields(Exception):
pass
class InvalidStructuredJSON(Exception):
pass
class InvalidHeadersFormat(Exception):
pass

View File

@ -18,7 +18,9 @@ from cloudevents.http.event import CloudEvent
from cloudevents.http.event_type import is_binary, is_structured
from cloudevents.http.http_methods import (
from_http,
to_binary,
to_binary_http,
to_structured,
to_structured_http,
)
from cloudevents.http.json_methods import from_json, to_json

View File

@ -58,15 +58,15 @@ class CloudEvent:
).isoformat()
if self._attributes["specversion"] not in _required_by_version:
raise cloud_exceptions.CloudEventMissingRequiredFields(
f"Invalid specversion: {self._attributes['specversion']}"
raise cloud_exceptions.MissingRequiredFields(
f"Invalid specversion: {self._attributes['specversion']}. "
)
# There is no good way to default 'source' and 'type', so this
# checks for those (or any new required attributes).
required_set = _required_by_version[self._attributes["specversion"]]
if not required_set <= self._attributes.keys():
raise cloud_exceptions.CloudEventMissingRequiredFields(
f"Missing required keys: {required_set - attributes.keys()}"
raise cloud_exceptions.MissingRequiredFields(
f"Missing required keys: {required_set - self._attributes.keys()}. "
)
def __eq__(self, other):

View File

@ -1,6 +1,8 @@
import json
import typing
from deprecation import deprecated
import cloudevents.exceptions as cloud_exceptions
from cloudevents.http.event import CloudEvent
from cloudevents.http.event_type import is_binary, is_structured
@ -10,20 +12,30 @@ from cloudevents.sdk import converters, marshaller, types
def from_http(
data: typing.Union[str, bytes],
headers: typing.Dict[str, str],
data: typing.Union[str, bytes, None],
data_unmarshaller: types.UnmarshallerType = None,
):
"""
Unwrap a CloudEvent (binary or structured) from an HTTP request.
:param data: the HTTP request body
:type data: typing.IO
:param headers: the HTTP headers
:type headers: typing.Dict[str, str]
:param data: the HTTP request body
:type data: typing.IO
:param data_unmarshaller: Callable function to map data to a python object
e.g. lambda x: x or lambda x: json.loads(x)
:type data_unmarshaller: types.UnmarshallerType
"""
if data is None:
data = ""
if not isinstance(data, (str, bytes, bytearray)):
raise cloud_exceptions.InvalidStructuredJSON(
"Expected json of type (str, bytes, bytearray), "
f"but instead found {type(data)}. "
)
headers = {key.lower(): value for key, value in headers.items()}
if data_unmarshaller is None:
data_unmarshaller = _json_or_string
@ -32,19 +44,25 @@ def from_http(
if is_binary(headers):
specversion = headers.get("ce-specversion", None)
else:
raw_ce = json.loads(data)
try:
raw_ce = json.loads(data)
except json.decoder.JSONDecodeError:
raise cloud_exceptions.InvalidStructuredJSON(
"Failed to read fields from structured event. "
f"The following can not be parsed as json: {data}. "
)
specversion = raw_ce.get("specversion", None)
if specversion is None:
raise cloud_exceptions.CloudEventMissingRequiredFields(
"could not find specversion in HTTP request"
raise cloud_exceptions.MissingRequiredFields(
"Failed to find specversion in HTTP request. "
)
event_handler = _obj_by_version.get(specversion, None)
if event_handler is None:
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
f"found invalid specversion {specversion}"
raise cloud_exceptions.InvalidRequiredFields(
f"Found invalid specversion {specversion}. "
)
event = marshall.FromRequest(
@ -77,8 +95,8 @@ def _to_http(
data_marshaller = _marshaller_by_format[format]
if event._attributes["specversion"] not in _obj_by_version:
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
f"Unsupported specversion: {event._attributes['specversion']}"
raise cloud_exceptions.InvalidRequiredFields(
f"Unsupported specversion: {event._attributes['specversion']}. "
)
event_handler = _obj_by_version[event._attributes["specversion"]]()
@ -91,11 +109,13 @@ def _to_http(
)
def to_structured_http(
def to_structured(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
Returns a tuple of HTTP headers/body dicts representing this cloudevent
Returns a tuple of HTTP headers/body dicts representing this cloudevent. If
event.data is a byte object, body will have a data_base64 field instead of
data.
:param event: CloudEvent to cast into http data
:type event: CloudEvent
@ -107,7 +127,7 @@ def to_structured_http(
return _to_http(event=event, data_marshaller=data_marshaller)
def to_binary_http(
def to_binary(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
"""
@ -125,3 +145,17 @@ def to_binary_http(
format=converters.TypeBinary,
data_marshaller=data_marshaller,
)
@deprecated(deprecated_in="1.0.2", details="Use to_binary function instead")
def to_binary_http(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
return to_binary(event, data_marshaller)
@deprecated(deprecated_in="1.0.2", details="Use to_structured function instead")
def to_structured_http(
event: CloudEvent, data_marshaller: types.MarshallerType = None,
) -> (dict, typing.Union[bytes, str]):
return to_structured(event, data_marshaller)

View File

@ -1,7 +1,7 @@
import typing
from cloudevents.http.event import CloudEvent
from cloudevents.http.http_methods import from_http, to_structured_http
from cloudevents.http.http_methods import from_http, to_structured
from cloudevents.sdk import types
@ -17,7 +17,7 @@ def to_json(
:type data_marshaller: typing.Callable
:returns: json object representing the given event
"""
return to_structured_http(event, data_marshaller=data_marshaller)[1]
return to_structured(event, data_marshaller=data_marshaller)[1]
def from_json(
@ -33,4 +33,4 @@ def from_json(
:type data_unmarshaller: typing.Callable
:returns: CloudEvent representing given cloudevent json object
"""
return from_http(data=data, headers={}, data_unmarshaller=data_unmarshaller)
return from_http(headers={}, data=data, data_unmarshaller=data_unmarshaller)

View File

@ -12,7 +12,7 @@ def default_marshaller(content: any):
def _json_or_string(content: typing.Union[str, bytes]):
if len(content) == 0:
if content is None or len(content) == 0:
return None
try:
return json.loads(content)

View File

@ -22,7 +22,7 @@ from cloudevents.sdk import types
# TODO(slinkydeveloper) is this really needed?
class EventGetterSetter(object):
class EventGetterSetter(object): # pragma: no cover
# ce-specversion
def CloudEventVersion(self) -> str:
@ -220,7 +220,7 @@ class BaseEvent(EventGetterSetter):
missing_fields = self._ce_required_fields - raw_ce.keys()
if len(missing_fields) > 0:
raise cloud_exceptions.CloudEventMissingRequiredFields(
raise cloud_exceptions.MissingRequiredFields(
f"Missing required attributes: {missing_fields}"
)
@ -246,7 +246,7 @@ class BaseEvent(EventGetterSetter):
missing_fields = required_binary_fields - headers.keys()
if len(missing_fields) > 0:
raise cloud_exceptions.CloudEventMissingRequiredFields(
raise cloud_exceptions.MissingRequiredFields(
f"Missing required attributes: {missing_fields}"
)

View File

@ -75,10 +75,6 @@ class Event(base.BaseEvent):
def ContentEncoding(self) -> str:
return self.ce__datacontentencoding.get()
@property
def datacontentencoding(self):
return self.ContentEncoding()
def SetEventType(self, eventType: str) -> base.BaseEvent:
self.Set("type", eventType)
return self
@ -119,6 +115,26 @@ class Event(base.BaseEvent):
self.Set("datacontentencoding", contentEncoding)
return self
@property
def datacontentencoding(self):
return self.ContentEncoding()
@datacontentencoding.setter
def datacontentencoding(self, value: str):
self.SetContentEncoding(value)
@property
def subject(self) -> str:
return self.Subject()
@subject.setter
def subject(self, value: str):
self.SetSubject(value)
@property
def schema_url(self) -> str:
return self.SchemaURL()
@schema_url.setter
def schema_url(self, value: str):
self.SetSchemaURL(value)

View File

@ -98,3 +98,19 @@ class Event(base.BaseEvent):
def SetExtensions(self, extensions: dict) -> base.BaseEvent:
self.Set("extensions", extensions)
return self
@property
def schema(self) -> str:
return self.Schema()
@schema.setter
def schema(self, value: str):
self.SetSchema(value)
@property
def subject(self) -> str:
return self.Subject()
@subject.setter
def subject(self, value: str):
self.SetSubject(value)

View File

@ -0,0 +1,33 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
import cloudevents.exceptions as cloud_exceptions
from cloudevents.sdk.event import v1, v03
@pytest.mark.parametrize("event_class", [v1.Event, v03.Event])
def test_unmarshall_binary_missing_fields(event_class):
event = event_class()
with pytest.raises(cloud_exceptions.MissingRequiredFields) as e:
event.UnmarshalBinary({}, "", lambda x: x)
assert "Missing required attributes: " in str(e.value)
@pytest.mark.parametrize("event_class", [v1.Event, v03.Event])
def test_get_nonexistent_optional(event_class):
event = event_class()
event.SetExtensions({"ext1": "val"})
res = event.Get("ext1")
assert res[0] == "val" and res[1] == True

View File

@ -0,0 +1,41 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.sdk import exceptions
from cloudevents.sdk.converters import base, binary, structured
def test_binary_converter_raise_unsupported():
with pytest.raises(exceptions.UnsupportedEvent):
cnvtr = binary.BinaryHTTPCloudEventConverter()
cnvtr.read(None, {}, None, None)
def test_base_converters_raise_exceptions():
with pytest.raises(Exception):
cnvtr = base.Converter()
cnvtr.event_supported(None)
with pytest.raises(Exception):
cnvtr = base.Converter()
cnvtr.can_read(None)
with pytest.raises(Exception):
cnvtr = base.Converter()
cnvtr.write(None, None)
with pytest.raises(Exception):
cnvtr = base.Converter()
cnvtr.read(None, None, None, None)

View File

@ -92,7 +92,6 @@ def test_general_structured_properties(event_class):
if key == "content-type":
assert new_headers[key] == http_headers[key]
continue
assert key in copy_of_ce
# Test setters
new_type = str(uuid4())

View File

@ -0,0 +1,37 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.http import (
CloudEvent,
to_binary,
to_binary_http,
to_structured,
to_structured_http,
)
@pytest.fixture
def event():
return CloudEvent({"source": "s", "type": "t"}, None)
def test_to_binary_http_deprecated(event):
with pytest.deprecated_call():
assert to_binary(event) == to_binary_http(event)
def test_to_structured_http_deprecated(event):
with pytest.deprecated_call():
assert to_structured(event) == to_structured_http(event)

View File

@ -15,12 +15,7 @@ import json
import pytest
from cloudevents.http import (
CloudEvent,
from_http,
to_binary_http,
to_structured_http,
)
from cloudevents.http import CloudEvent, from_http, to_binary, to_structured
test_data = json.dumps({"data-key": "val"})
test_attributes = {
@ -39,7 +34,7 @@ def test_cloudevent_access_extensions(specversion):
@pytest.mark.parametrize("specversion", ["0.3", "1.0"])
def test_to_binary_extensions(specversion):
event = CloudEvent(test_attributes, test_data)
headers, body = to_binary_http(event)
headers, body = to_binary(event)
assert "ce-ext1" in headers
assert headers.get("ce-ext1") == test_attributes["ext1"]
@ -56,7 +51,7 @@ def test_from_binary_extensions(specversion):
"ce-ext2": "test2",
}
body = json.dumps({"data-key": "val"})
event = from_http(body, headers)
event = from_http(headers, body)
assert headers["ce-ext1"] == event["ext1"]
assert headers["ce-ext2"] == event["ext2"]
@ -65,7 +60,7 @@ def test_from_binary_extensions(specversion):
@pytest.mark.parametrize("specversion", ["0.3", "1.0"])
def test_to_structured_extensions(specversion):
event = CloudEvent(test_attributes, test_data)
headers, body = to_structured_http(event)
headers, body = to_structured(event)
body = json.loads(body)
@ -86,7 +81,7 @@ def test_from_structured_extensions(specversion):
}
data = json.dumps(body)
event = from_http(data, headers)
event = from_http(headers, data)
assert body["ext1"] == event["ext1"]
assert body["ext2"] == event["ext2"]

View File

@ -61,4 +61,3 @@ def test_structured_event_to_request_upstream(event_class):
if key == "content-type":
assert new_headers[key] == http_headers[key]
continue
assert key in copy_of_ce

View File

@ -1,5 +1,6 @@
import pytest
import cloudevents.exceptions as cloud_exceptions
from cloudevents.http import CloudEvent
@ -69,3 +70,47 @@ def test_http_cloudevent_mutates_equality(specversion):
event3.data = '{"name":"paul"}'
assert event2 == event3
assert event1 != event2 and event3 != event1
def test_cloudevent_missing_specversion():
attributes = {"specversion": "0.2", "source": "s", "type": "t"}
with pytest.raises(cloud_exceptions.MissingRequiredFields) as e:
event = CloudEvent(attributes, None)
assert "Invalid specversion: 0.2" in str(e.value)
def test_cloudevent_missing_minimal_required_fields():
attributes = {"type": "t"}
with pytest.raises(cloud_exceptions.MissingRequiredFields) as e:
event = CloudEvent(attributes, None)
assert f"Missing required keys: {set(['source'])}" in str(e.value)
attributes = {"source": "s"}
with pytest.raises(cloud_exceptions.MissingRequiredFields) as e:
event = CloudEvent(attributes, None)
assert f"Missing required keys: {set(['type'])}" in str(e.value)
def test_cloudevent_general_overrides():
event = CloudEvent(
{
"source": "my-source",
"type": "com.test.overrides",
"subject": "my-subject",
},
None,
)
expected_attributes = [
"time",
"source",
"id",
"specversion",
"type",
"subject",
]
assert len(event) == 6
for attribute in expected_attributes:
assert attribute in event
del event[attribute]
assert len(event) == 0

View File

@ -25,7 +25,10 @@ from cloudevents.http import (
CloudEvent,
from_http,
is_binary,
is_structured,
to_binary,
to_binary_http,
to_structured,
to_structured_http,
)
from cloudevents.sdk import converters
@ -69,17 +72,13 @@ test_data = {"payload-content": "Hello World!"}
app = Sanic(__name__)
def post(url, headers, data):
return app.test_client.post(url, headers=headers, data=data)
@app.route("/event", ["POST"])
async def echo(request):
decoder = None
if "binary-payload" in request.headers:
decoder = lambda x: x
event = from_http(
request.body, headers=dict(request.headers), data_unmarshaller=decoder
dict(request.headers), request.body, data_unmarshaller=decoder
)
data = (
event.data
@ -91,25 +90,24 @@ async def echo(request):
@pytest.mark.parametrize("body", invalid_cloudevent_request_body)
def test_missing_required_fields_structured(body):
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
with pytest.raises(cloud_exceptions.MissingRequiredFields):
# CloudEvent constructor throws TypeError if missing required field
# and NotImplementedError because structured calls aren't
# implemented. In this instance one of the required keys should have
# prefix e-id instead of ce-id therefore it should throw
_ = from_http(
json.dumps(body),
headers={"Content-Type": "application/cloudevents+json"},
{"Content-Type": "application/cloudevents+json"}, json.dumps(body),
)
@pytest.mark.parametrize("headers", invalid_test_headers)
def test_missing_required_fields_binary(headers):
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
with pytest.raises(cloud_exceptions.MissingRequiredFields):
# CloudEvent constructor throws TypeError if missing required field
# and NotImplementedError because structured calls aren't
# implemented. In this instance one of the required keys should have
# prefix e-id instead of ce-id therefore it should throw
_ = from_http(json.dumps(test_data), headers=headers)
_ = from_http(headers, json.dumps(test_data))
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
@ -177,9 +175,9 @@ def test_roundtrip_non_json_event(converter, specversion):
event = CloudEvent(attrs, compressed_data)
if converter == converters.TypeStructured:
headers, data = to_structured_http(event, data_marshaller=lambda x: x)
headers, data = to_structured(event, data_marshaller=lambda x: x)
elif converter == converters.TypeBinary:
headers, data = to_binary_http(event, data_marshaller=lambda x: x)
headers, data = to_binary(event, data_marshaller=lambda x: x)
headers["binary-payload"] = "true" # Decoding hint for server
_, r = app.test_client.post("/event", headers=headers, data=data)
@ -204,12 +202,12 @@ def test_missing_ce_prefix_binary_event(specversion):
# breaking prefix e.g. e-id instead of ce-id
prefixed_headers[key[1:]] = headers[key]
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
with pytest.raises(cloud_exceptions.MissingRequiredFields):
# CloudEvent constructor throws TypeError if missing required field
# and NotImplementedError because structured calls aren't
# implemented. In this instance one of the required keys should have
# prefix e-id instead of ce-id therefore it should throw
_ = from_http(json.dumps(test_data), headers=prefixed_headers)
_ = from_http(prefixed_headers, json.dumps(test_data))
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
@ -226,7 +224,7 @@ def test_valid_binary_events(specversion):
"ce-specversion": specversion,
}
data = {"payload": f"payload-{i}"}
events_queue.append(from_http(json.dumps(data), headers=headers))
events_queue.append(from_http(headers, json.dumps(data)))
for i, event in enumerate(events_queue):
data = event.data
@ -247,7 +245,7 @@ def test_structured_to_request(specversion):
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body_bytes = to_structured_http(event)
headers, body_bytes = to_structured(event)
assert isinstance(body_bytes, bytes)
body = json.loads(body_bytes)
@ -267,7 +265,7 @@ def test_binary_to_request(specversion):
}
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body_bytes = to_binary_http(event)
headers, body_bytes = to_binary(event)
body = json.loads(body_bytes)
for key in data:
@ -289,7 +287,7 @@ def test_empty_data_structured_event(specversion):
}
_ = from_http(
json.dumps(attributes), {"content-type": "application/cloudevents+json"}
{"content-type": "application/cloudevents+json"}, json.dumps(attributes)
)
@ -304,7 +302,7 @@ def test_empty_data_binary_event(specversion):
"ce-time": "2018-10-23T12:28:22.4579346Z",
"ce-source": "<source-url>",
}
_ = from_http("", headers)
_ = from_http(headers, "")
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
@ -322,8 +320,8 @@ def test_valid_structured_events(specversion):
}
events_queue.append(
from_http(
json.dumps(event),
{"content-type": "application/cloudevents+json"},
json.dumps(event),
)
)
@ -344,7 +342,7 @@ def test_structured_no_content_type(specversion):
"specversion": specversion,
"data": test_data,
}
event = from_http(json.dumps(data), {},)
event = from_http({}, json.dumps(data))
assert event["id"] == "id"
assert event["source"] == "source.com.test"
@ -382,7 +380,7 @@ def test_cloudevent_repr(specversion):
"ce-time": "2018-10-23T12:28:22.4579346Z",
"ce-source": "<source-url>",
}
event = from_http("", headers)
event = from_http(headers, "")
# Testing to make sure event is printable. I could runevent. __repr__() but
# we had issues in the past where event.__repr__() could run but
# print(event) would fail.
@ -398,5 +396,79 @@ def test_none_data_cloudevent(specversion):
"specversion": specversion,
}
)
to_binary_http(event)
to_structured_http(event)
to_binary(event)
to_structured(event)
def test_wrong_specversion():
headers = {"Content-Type": "application/cloudevents+json"}
data = json.dumps(
{
"specversion": "0.2",
"type": "word.found.name",
"id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
"source": "<my-source>",
}
)
with pytest.raises(cloud_exceptions.InvalidRequiredFields) as e:
from_http(headers, data)
assert "Found invalid specversion 0.2" in str(e.value)
def test_invalid_data_format_structured_from_http():
headers = {"Content-Type": "application/cloudevents+json"}
data = 20
with pytest.raises(cloud_exceptions.InvalidStructuredJSON) as e:
from_http(headers, data)
assert "Expected json of type (str, bytes, bytearray)" in str(e.value)
def test_wrong_specversion_to_request():
event = CloudEvent({"source": "s", "type": "t"}, None)
with pytest.raises(cloud_exceptions.InvalidRequiredFields) as e:
event["specversion"] = "0.2"
to_binary(event)
assert "Unsupported specversion: 0.2" in str(e.value)
def test_is_structured():
headers = {
"Content-Type": "application/cloudevents+json",
}
assert is_structured(headers)
headers = {
"ce-id": "my-id",
"ce-source": "<event-source>",
"ce-type": "cloudevent.event.type",
"ce-specversion": "1.0",
"Content-Type": "text/plain",
}
assert not is_structured(headers)
def test_empty_json_structured():
headers = {"Content-Type": "application/cloudevents+json"}
data = ""
with pytest.raises(cloud_exceptions.InvalidStructuredJSON) as e:
from_http(
headers, data,
)
assert "Failed to read fields from structured event. " in str(e.value)
def test_uppercase_headers_with_none_data_binary():
headers = {
"Ce-Id": "my-id",
"Ce-Source": "<event-source>",
"Ce-Type": "cloudevent.event.type",
"Ce-Specversion": "1.0",
}
event = from_http(headers, None)
for key in headers:
assert event[key.lower()[3:]] == headers[key]
assert event.data == None
_, new_data = to_binary(event)
assert new_data == None

View File

@ -0,0 +1,63 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.sdk import converters, exceptions, marshaller
from cloudevents.sdk.converters import binary, structured
from cloudevents.sdk.event import v1
@pytest.fixture
def headers():
return {
"ce-specversion": "1.0",
"ce-source": "1.0",
"ce-type": "com.marshaller.test",
"ce-id": "1234-1234-1234",
}
def test_from_request_wrong_unmarshaller():
with pytest.raises(exceptions.InvalidDataUnmarshaller):
m = marshaller.NewDefaultHTTPMarshaller()
_ = m.FromRequest(v1.Event(), {}, "", None)
def test_to_request_wrong_marshaller():
with pytest.raises(exceptions.InvalidDataMarshaller):
m = marshaller.NewDefaultHTTPMarshaller()
_ = m.ToRequest(v1.Event(), data_marshaller="")
def test_from_request_cannot_read(headers):
with pytest.raises(exceptions.UnsupportedEventConverter):
m = marshaller.HTTPMarshaller(
[binary.NewBinaryHTTPCloudEventConverter(),]
)
m.FromRequest(v1.Event(), {}, "")
with pytest.raises(exceptions.UnsupportedEventConverter):
m = marshaller.HTTPMarshaller(
[structured.NewJSONHTTPCloudEventConverter()]
)
m.FromRequest(v1.Event(), headers, "")
def test_to_request_invalid_converter():
with pytest.raises(exceptions.NoSuchConverter):
m = marshaller.HTTPMarshaller(
[structured.NewJSONHTTPCloudEventConverter()]
)
m.ToRequest(v1.Event(), "")

View File

@ -0,0 +1,36 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.sdk.event.opt import Option
def test_set_raise_error():
with pytest.raises(ValueError):
o = Option("test", "value", True)
o.set(None)
def test_options_eq_override():
o = Option("test", "value", True)
assert o.required()
o2 = Option("test", "value", True)
assert o2.required()
assert o == o2
o.set("setting to new value")
assert o != o2

View File

@ -0,0 +1,64 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.sdk.event import v03
def test_v03_time_property():
event = v03.Event()
time1 = "1234"
event.time = time1
assert event.EventTime() == time1
time2 = "4321"
event.SetEventTime(time2)
assert event.time == time2
def test_v03_subject_property():
event = v03.Event()
subject1 = "<my-subject>"
event.subject = subject1
assert event.Subject() == subject1
subject2 = "<my-subject2>"
event.SetSubject(subject2)
assert event.subject == subject2
def test_v03_schema_url_property():
event = v03.Event()
schema_url1 = "<my-schema>"
event.schema_url = schema_url1
assert event.SchemaURL() == schema_url1
schema_url2 = "<my-schema2>"
event.SetSchemaURL(schema_url2)
assert event.schema_url == schema_url2
def test_v03_datacontentencoding_property():
event = v03.Event()
datacontentencoding1 = "<my-datacontentencoding>"
event.datacontentencoding = datacontentencoding1
assert event.ContentEncoding() == datacontentencoding1
datacontentencoding2 = "<my-datacontentencoding2>"
event.SetContentEncoding(datacontentencoding2)
assert event.datacontentencoding == datacontentencoding2

View File

@ -0,0 +1,53 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from cloudevents.sdk.event import v1
def test_v1_time_property():
event = v1.Event()
time1 = "1234"
event.time = time1
assert event.EventTime() == time1
time2 = "4321"
event.SetEventTime(time2)
assert event.time == time2
def test_v1_subject_property():
event = v1.Event()
subject1 = "<my-subject>"
event.subject = subject1
assert event.Subject() == subject1
subject2 = "<my-subject2>"
event.SetSubject(subject2)
assert event.subject == subject2
def test_v1_schema_property():
event = v1.Event()
schema1 = "<my-schema>"
event.schema = schema1
assert event.Schema() == schema1
schema2 = "<my-schema2>"
event.SetSchema(schema2)
assert event.schema == schema2

View File

@ -15,7 +15,7 @@ import sys
import requests
from cloudevents.http import CloudEvent, to_binary_http, to_structured_http
from cloudevents.http import CloudEvent, to_binary, to_structured
resp = requests.get(
"https://raw.githubusercontent.com/cncf/artwork/master/projects/cloudevents/horizontal/color/cloudevents-horizontal-color.png"
@ -33,7 +33,7 @@ def send_binary_cloud_event(url: str):
event = CloudEvent(attributes, image_bytes)
# Create cloudevent HTTP headers and content
headers, body = to_binary_http(event)
headers, body = to_binary(event)
# Send cloudevent
requests.post(url, headers=headers, data=body)
@ -50,10 +50,10 @@ def send_structured_cloud_event(url: str):
event = CloudEvent(attributes, image_bytes)
# Create cloudevent HTTP headers and content
# Note that to_structured_http will create a data_base64 data field in
# Note that to_structured will create a data_base64 data field in
# specversion 1.0 (default specversion) if given
# an event whose data field is of type bytes.
headers, body = to_structured_http(event)
headers, body = to_structured(event)
# Send cloudevent
requests.post(url, headers=headers, data=body)

View File

@ -26,8 +26,8 @@ def home():
# Create a CloudEvent.
# data_unmarshaller will cast event.data into an io.BytesIO object
event = from_http(
request.get_data(),
request.headers,
request.get_data(),
data_unmarshaller=lambda x: io.BytesIO(x),
)

View File

@ -7,12 +7,7 @@ from client import image_bytes
from image_sample_server import app
from PIL import Image
from cloudevents.http import (
CloudEvent,
from_http,
to_binary_http,
to_structured_http,
)
from cloudevents.http import CloudEvent, from_http, to_binary, to_structured
image_fileobj = io.BytesIO(image_bytes)
image_expected_shape = (1880, 363)
@ -35,11 +30,11 @@ def test_create_binary_image():
event = CloudEvent(attributes, image_bytes)
# Create http headers/body content
headers, body = to_binary_http(event)
headers, body = to_binary(event)
# Unmarshall CloudEvent and re-create image
reconstruct_event = from_http(
body, headers, data_unmarshaller=lambda x: io.BytesIO(x)
headers, body, data_unmarshaller=lambda x: io.BytesIO(x)
)
# reconstruct_event.data is an io.BytesIO object due to data_unmarshaller
@ -62,7 +57,7 @@ def test_create_structured_image():
event = CloudEvent(attributes, image_bytes)
# Create http headers/body content
headers, body = to_structured_http(event)
headers, body = to_structured(event)
# Structured has cloudevent attributes marshalled inside the body. For this
# reason we must load the byte object to create the python dict containing
@ -75,7 +70,7 @@ def test_create_structured_image():
# Unmarshall CloudEvent and re-create image
reconstruct_event = from_http(
body, headers, data_unmarshaller=lambda x: io.BytesIO(x)
headers, body, data_unmarshaller=lambda x: io.BytesIO(x)
)
# reconstruct_event.data is an io.BytesIO object due to data_unmarshaller
@ -92,10 +87,10 @@ def test_server_structured(client):
event = CloudEvent(attributes, image_bytes)
# Create cloudevent HTTP headers and content
# Note that to_structured_http will create a data_base64 data field in
# Note that to_structured will create a data_base64 data field in
# specversion 1.0 (default specversion) if given
# an event whose data field is of type bytes.
headers, body = to_structured_http(event)
headers, body = to_structured(event)
# Send cloudevent
r = client.post("/", headers=headers, data=body)
@ -113,7 +108,7 @@ def test_server_binary(client):
event = CloudEvent(attributes, image_bytes)
# Create cloudevent HTTP headers and content
headers, body = to_binary_http(event)
headers, body = to_binary(event)
# Send cloudevent
r = client.post("/", headers=headers, data=body)

View File

@ -16,7 +16,7 @@ import sys
import requests
from cloudevents.http import CloudEvent, to_binary_http, to_structured_http
from cloudevents.http import CloudEvent, to_binary, to_structured
def send_binary_cloud_event(url):
@ -28,7 +28,7 @@ def send_binary_cloud_event(url):
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_binary_http(event)
headers, body = to_binary(event)
# send and print event
requests.post(url, headers=headers, data=body)
@ -44,7 +44,7 @@ def send_structured_cloud_event(url):
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_structured_http(event)
headers, body = to_structured(event)
# send and print event
requests.post(url, headers=headers, data=body)

View File

@ -22,7 +22,7 @@ app = Flask(__name__)
@app.route("/", methods=["POST"])
def home():
# create a CloudEvent
event = from_http(request.get_data(), request.headers)
event = from_http(request.headers, request.get_data())
# you can access cloudevent fields as seen below
print(

View File

@ -1,7 +1,7 @@
import pytest
from json_sample_server import app
from cloudevents.http import CloudEvent, to_binary_http, to_structured_http
from cloudevents.http import CloudEvent, to_binary, to_structured
@pytest.fixture
@ -19,7 +19,7 @@ def test_binary_request(client):
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_binary_http(event)
headers, body = to_binary(event)
r = client.post("/", headers=headers, data=body)
assert r.status_code == 204
@ -34,7 +34,7 @@ def test_structured_request(client):
data = {"message": "Hello World!"}
event = CloudEvent(attributes, data)
headers, body = to_structured_http(event)
headers, body = to_structured(event)
r = client.post("/", headers=headers, data=body)
assert r.status_code == 204

View File

@ -41,4 +41,5 @@ setup(
],
packages=find_packages(exclude=["cloudevents.tests"]),
version=pypi_config["version_target"],
install_requires=["deprecation>=2.0,<3.0"],
)

View File

@ -9,7 +9,7 @@ deps =
-r{toxinidir}/requirements/docs.txt
-r{toxinidir}/requirements/publish.txt
setenv =
PYTESTARGS = -v -s --tb=long --cov=cloudevents
PYTESTARGS = -v -s --tb=long --cov=cloudevents --cov-report term-missing --cov-fail-under=100
commands = pytest {env:PYTESTARGS} {posargs}
[testenv:reformat]