cloudevents version 1.0.1 release (#102)
* docs: rename receiving cloudevents (#91) Signed-off-by: Grant Timmerman <timmerman+devrel@google.com> * add coc ref (#90) Signed-off-by: Doug Davis <dug@us.ibm.com> Co-authored-by: Curtis Mason <31265687+cumason123@users.noreply.github.com> * CloudEvents equality override (#98) * added tests to cloudevent eq Signed-off-by: Curtis Mason <cumason@google.com> * lint fix Signed-off-by: Curtis Mason <cumason@google.com> * modified changelog Signed-off-by: Curtis Mason <cumason@google.com> * version bump Signed-off-by: Curtis Mason <cumason@google.com> * cloudevent fields type checking adjustments (#97) * added exceptions and more indepth can_read Signed-off-by: Curtis Mason <cumason@google.com> * moved is_binary, is_structured into http module Signed-off-by: Curtis Mason <cumason@google.com> * changelog and version bump Signed-off-by: Curtis Mason <cumason@google.com> * removed unused import and spacing Signed-off-by: Curtis Mason <cumason@google.com> * lint fix Signed-off-by: Curtis Mason <cumason@google.com> * reverted auto format change Signed-off-by: Curtis Mason <cumason@google.com> * reverted changelog and auto format changes Signed-off-by: Curtis Mason <cumason@google.com> * changelog 1.0.1 update (#101) Signed-off-by: Curtis Mason <cumason@google.com> Co-authored-by: Grant Timmerman <timmerman@google.com> Co-authored-by: Doug Davis <dug@us.ibm.com>
This commit is contained in:
parent
390134c2b9
commit
d95b1303a9
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -4,10 +4,21 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.1]
|
||||||
|
### Added
|
||||||
|
- CloudEvent exceptions and event type checking in http module ([#96])
|
||||||
|
- CloudEvent equality override ([#98])
|
||||||
|
|
||||||
## [1.0.0]
|
## [1.0.0]
|
||||||
### Added
|
### Added
|
||||||
|
- Update types and handle data_base64 structured ([#34])
|
||||||
- Added a user friendly CloudEvent class with data validation ([#36])
|
- Added a user friendly CloudEvent class with data validation ([#36])
|
||||||
- CloudEvent structured cloudevent support ([#47])
|
- CloudEvent structured cloudevent support ([#47])
|
||||||
|
- Separated http methods into cloudevents.http module ([#60])
|
||||||
|
- Implemented to_json and from_json in http module ([#72])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed top level extensions bug ([#71])
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Removed support for Cloudevents V0.2 and V0.1 ([#43])
|
- Removed support for Cloudevents V0.2 and V0.1 ([#43])
|
||||||
|
|
@ -74,6 +85,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
[#23]: https://github.com/cloudevents/sdk-python/pull/23
|
[#23]: https://github.com/cloudevents/sdk-python/pull/23
|
||||||
[#25]: https://github.com/cloudevents/sdk-python/pull/25
|
[#25]: https://github.com/cloudevents/sdk-python/pull/25
|
||||||
[#27]: https://github.com/cloudevents/sdk-python/pull/27
|
[#27]: https://github.com/cloudevents/sdk-python/pull/27
|
||||||
|
[#34]: https://github.com/cloudevents/sdk-python/pull/34
|
||||||
[#36]: https://github.com/cloudevents/sdk-python/pull/36
|
[#36]: https://github.com/cloudevents/sdk-python/pull/36
|
||||||
[#43]: https://github.com/cloudevents/sdk-python/pull/43
|
[#43]: https://github.com/cloudevents/sdk-python/pull/43
|
||||||
[#47]: https://github.com/cloudevents/sdk-python/pull/47
|
[#47]: https://github.com/cloudevents/sdk-python/pull/47
|
||||||
|
[#60]: https://github.com/cloudevents/sdk-python/pull/60
|
||||||
|
[#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
|
||||||
13
README.md
13
README.md
|
|
@ -64,7 +64,7 @@ requests.post("<some-url>", data=body, headers=headers)
|
||||||
|
|
||||||
You can find a complete example of turning a CloudEvent into a HTTP request [in the samples directory](samples/http-json-cloudevents/client.py).
|
You can find a complete example of turning a CloudEvent into a HTTP request [in the samples directory](samples/http-json-cloudevents/client.py).
|
||||||
|
|
||||||
#### Request to CloudEvent
|
## Receiving CloudEvents
|
||||||
|
|
||||||
The code below shows how to consume a cloudevent using the popular python web framework
|
The code below shows how to consume a cloudevent using the popular python web framework
|
||||||
[flask](https://flask.palletsprojects.com/en/1.1.x/quickstart/):
|
[flask](https://flask.palletsprojects.com/en/1.1.x/quickstart/):
|
||||||
|
|
@ -120,6 +120,17 @@ the same API. It will use semantic versioning with following rules:
|
||||||
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
|
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
|
||||||
- Contact for additional information: Denis Makogon (`@denysmakogon` on slack).
|
- Contact for additional information: Denis Makogon (`@denysmakogon` on slack).
|
||||||
|
|
||||||
|
Each SDK may have its own unique processes, tooling and guidelines, common
|
||||||
|
governance related material can be found in the
|
||||||
|
[CloudEvents `community`](https://github.com/cloudevents/spec/tree/master/community)
|
||||||
|
directory. In particular, in there you will find information concerning
|
||||||
|
how SDK projects are
|
||||||
|
[managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
|
||||||
|
[guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
|
||||||
|
for how PR reviews and approval, and our
|
||||||
|
[Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information)
|
||||||
|
information.
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|
||||||
We use black and isort for autoformatting. We setup a tox environment to reformat
|
We use black and isort for autoformatting. We setup a tox environment to reformat
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.1"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# 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.
|
||||||
|
class CloudEventMissingRequiredFields(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CloudEventTypeErrorRequiredFields(Exception):
|
||||||
|
pass
|
||||||
|
|
@ -15,6 +15,7 @@ import json
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from cloudevents.http.event import CloudEvent
|
from cloudevents.http.event import CloudEvent
|
||||||
|
from cloudevents.http.event_type import is_binary, is_structured
|
||||||
from cloudevents.http.http_methods import (
|
from cloudevents.http.http_methods import (
|
||||||
from_http,
|
from_http,
|
||||||
to_binary_http,
|
to_binary_http,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import datetime
|
||||||
import typing
|
import typing
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import cloudevents.exceptions as cloud_exceptions
|
||||||
from cloudevents.http.mappings import _required_by_version
|
from cloudevents.http.mappings import _required_by_version
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,17 +58,20 @@ class CloudEvent:
|
||||||
).isoformat()
|
).isoformat()
|
||||||
|
|
||||||
if self._attributes["specversion"] not in _required_by_version:
|
if self._attributes["specversion"] not in _required_by_version:
|
||||||
raise ValueError(
|
raise cloud_exceptions.CloudEventMissingRequiredFields(
|
||||||
f"Invalid specversion: {self._attributes['specversion']}"
|
f"Invalid specversion: {self._attributes['specversion']}"
|
||||||
)
|
)
|
||||||
# There is no good way to default 'source' and 'type', so this
|
# There is no good way to default 'source' and 'type', so this
|
||||||
# checks for those (or any new required attributes).
|
# checks for those (or any new required attributes).
|
||||||
required_set = _required_by_version[self._attributes["specversion"]]
|
required_set = _required_by_version[self._attributes["specversion"]]
|
||||||
if not required_set <= self._attributes.keys():
|
if not required_set <= self._attributes.keys():
|
||||||
raise ValueError(
|
raise cloud_exceptions.CloudEventMissingRequiredFields(
|
||||||
f"Missing required keys: {required_set - attributes.keys()}"
|
f"Missing required keys: {required_set - attributes.keys()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.data == other.data and self._attributes == other._attributes
|
||||||
|
|
||||||
# Data access is handled via `.data` member
|
# Data access is handled via `.data` member
|
||||||
# Attribute access is managed via Mapping type
|
# Attribute access is managed via Mapping type
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from cloudevents.sdk.converters import binary, structured
|
||||||
|
|
||||||
|
|
||||||
|
def is_binary(headers: typing.Dict[str, str]) -> bool:
|
||||||
|
"""Uses internal marshallers to determine whether this event is binary
|
||||||
|
:param headers: the HTTP headers
|
||||||
|
:type headers: typing.Dict[str, str]
|
||||||
|
:returns bool: returns a bool indicating whether the headers indicate a binary event type
|
||||||
|
"""
|
||||||
|
headers = {key.lower(): value for key, value in headers.items()}
|
||||||
|
content_type = headers.get("content-type", "")
|
||||||
|
binary_parser = binary.BinaryHTTPCloudEventConverter()
|
||||||
|
return binary_parser.can_read(content_type=content_type, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def is_structured(headers: typing.Dict[str, str]) -> bool:
|
||||||
|
"""Uses internal marshallers to determine whether this event is structured
|
||||||
|
:param headers: the HTTP headers
|
||||||
|
:type headers: typing.Dict[str, str]
|
||||||
|
:returns bool: returns a bool indicating whether the headers indicate a structured event type
|
||||||
|
"""
|
||||||
|
headers = {key.lower(): value for key, value in headers.items()}
|
||||||
|
content_type = headers.get("content-type", "")
|
||||||
|
structured_parser = structured.JSONHTTPCloudEventConverter()
|
||||||
|
return structured_parser.can_read(
|
||||||
|
content_type=content_type, headers=headers
|
||||||
|
)
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import json
|
import json
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import cloudevents.exceptions as cloud_exceptions
|
||||||
from cloudevents.http.event import CloudEvent
|
from cloudevents.http.event import CloudEvent
|
||||||
|
from cloudevents.http.event_type import is_binary, is_structured
|
||||||
from cloudevents.http.mappings import _marshaller_by_format, _obj_by_version
|
from cloudevents.http.mappings import _marshaller_by_format, _obj_by_version
|
||||||
from cloudevents.http.util import _json_or_string
|
from cloudevents.http.util import _json_or_string
|
||||||
from cloudevents.sdk import converters, marshaller, types
|
from cloudevents.sdk import converters, marshaller, types
|
||||||
|
|
@ -27,19 +29,23 @@ def from_http(
|
||||||
|
|
||||||
marshall = marshaller.NewDefaultHTTPMarshaller()
|
marshall = marshaller.NewDefaultHTTPMarshaller()
|
||||||
|
|
||||||
if converters.is_binary(headers):
|
if is_binary(headers):
|
||||||
specversion = headers.get("ce-specversion", None)
|
specversion = headers.get("ce-specversion", None)
|
||||||
else:
|
else:
|
||||||
raw_ce = json.loads(data)
|
raw_ce = json.loads(data)
|
||||||
specversion = raw_ce.get("specversion", None)
|
specversion = raw_ce.get("specversion", None)
|
||||||
|
|
||||||
if specversion is None:
|
if specversion is None:
|
||||||
raise ValueError("could not find specversion in HTTP request")
|
raise cloud_exceptions.CloudEventMissingRequiredFields(
|
||||||
|
"could not find specversion in HTTP request"
|
||||||
|
)
|
||||||
|
|
||||||
event_handler = _obj_by_version.get(specversion, None)
|
event_handler = _obj_by_version.get(specversion, None)
|
||||||
|
|
||||||
if event_handler is None:
|
if event_handler is None:
|
||||||
raise ValueError(f"found invalid specversion {specversion}")
|
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
|
||||||
|
f"found invalid specversion {specversion}"
|
||||||
|
)
|
||||||
|
|
||||||
event = marshall.FromRequest(
|
event = marshall.FromRequest(
|
||||||
event_handler(), headers, data, data_unmarshaller=data_unmarshaller
|
event_handler(), headers, data, data_unmarshaller=data_unmarshaller
|
||||||
|
|
@ -71,7 +77,7 @@ def _to_http(
|
||||||
data_marshaller = _marshaller_by_format[format]
|
data_marshaller = _marshaller_by_format[format]
|
||||||
|
|
||||||
if event._attributes["specversion"] not in _obj_by_version:
|
if event._attributes["specversion"] not in _obj_by_version:
|
||||||
raise ValueError(
|
raise cloud_exceptions.CloudEventTypeErrorRequiredFields(
|
||||||
f"Unsupported specversion: {event._attributes['specversion']}"
|
f"Unsupported specversion: {event._attributes['specversion']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,36 +11,7 @@
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from cloudevents.sdk.converters import binary, structured
|
from cloudevents.sdk.converters import binary, structured
|
||||||
|
|
||||||
TypeBinary = binary.BinaryHTTPCloudEventConverter.TYPE
|
TypeBinary = binary.BinaryHTTPCloudEventConverter.TYPE
|
||||||
TypeStructured = structured.JSONHTTPCloudEventConverter.TYPE
|
TypeStructured = structured.JSONHTTPCloudEventConverter.TYPE
|
||||||
|
|
||||||
|
|
||||||
def is_binary(headers: typing.Dict[str, str]) -> bool:
|
|
||||||
"""Uses internal marshallers to determine whether this event is binary
|
|
||||||
:param headers: the HTTP headers
|
|
||||||
:type headers: typing.Dict[str, str]
|
|
||||||
:returns bool: returns a bool indicating whether the headers indicate a binary event type
|
|
||||||
"""
|
|
||||||
headers = {key.lower(): value for key, value in headers.items()}
|
|
||||||
content_type = headers.get("content-type", "")
|
|
||||||
binary_parser = binary.BinaryHTTPCloudEventConverter()
|
|
||||||
return binary_parser.can_read(content_type=content_type, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
def is_structured(headers: typing.Dict[str, str]) -> bool:
|
|
||||||
"""Uses internal marshallers to determine whether this event is structured
|
|
||||||
:param headers: the HTTP headers
|
|
||||||
:type headers: typing.Dict[str, str]
|
|
||||||
:returns bool: returns a bool indicating whether the headers indicate a structured event type
|
|
||||||
"""
|
|
||||||
headers = {key.lower(): value for key, value in headers.items()}
|
|
||||||
content_type = headers.get("content-type", "")
|
|
||||||
structured_parser = structured.JSONHTTPCloudEventConverter()
|
|
||||||
return structured_parser.can_read(
|
|
||||||
content_type=content_type, headers=headers
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import typing
|
||||||
|
|
||||||
from cloudevents.sdk import exceptions, types
|
from cloudevents.sdk import exceptions, types
|
||||||
from cloudevents.sdk.converters import base
|
from cloudevents.sdk.converters import base
|
||||||
from cloudevents.sdk.converters.structured import JSONHTTPCloudEventConverter
|
from cloudevents.sdk.converters.util import has_binary_headers
|
||||||
from cloudevents.sdk.event import base as event_base
|
from cloudevents.sdk.event import base as event_base
|
||||||
from cloudevents.sdk.event import v1, v03
|
from cloudevents.sdk.event import v1, v03
|
||||||
|
|
||||||
|
|
@ -28,13 +28,11 @@ class BinaryHTTPCloudEventConverter(base.Converter):
|
||||||
|
|
||||||
def can_read(
|
def can_read(
|
||||||
self,
|
self,
|
||||||
content_type: str,
|
content_type: str = None,
|
||||||
headers: typing.Dict[str, str] = {"ce-specversion": None},
|
headers: typing.Dict[str, str] = {"ce-specversion": None},
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return ("ce-specversion" in headers) and not (
|
|
||||||
isinstance(content_type, str)
|
return has_binary_headers(headers)
|
||||||
and content_type.startswith(JSONHTTPCloudEventConverter.MIME_TYPE)
|
|
||||||
)
|
|
||||||
|
|
||||||
def event_supported(self, event: object) -> bool:
|
def event_supported(self, event: object) -> bool:
|
||||||
return type(event) in self.SUPPORTED_VERSIONS
|
return type(event) in self.SUPPORTED_VERSIONS
|
||||||
|
|
|
||||||
|
|
@ -16,23 +16,24 @@ import typing
|
||||||
|
|
||||||
from cloudevents.sdk import types
|
from cloudevents.sdk import types
|
||||||
from cloudevents.sdk.converters import base
|
from cloudevents.sdk.converters import base
|
||||||
|
from cloudevents.sdk.converters.util import has_binary_headers
|
||||||
from cloudevents.sdk.event import base as event_base
|
from cloudevents.sdk.event import base as event_base
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Singleton?
|
||||||
class JSONHTTPCloudEventConverter(base.Converter):
|
class JSONHTTPCloudEventConverter(base.Converter):
|
||||||
|
|
||||||
TYPE = "structured"
|
TYPE = "structured"
|
||||||
MIME_TYPE = "application/cloudevents+json"
|
MIME_TYPE = "application/cloudevents+json"
|
||||||
|
|
||||||
def can_read(
|
def can_read(
|
||||||
self,
|
self, content_type: str, headers: typing.Dict[str, str] = {},
|
||||||
content_type: str,
|
|
||||||
headers: typing.Dict[str, str] = {"ce-specversion": None},
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
return (
|
return (
|
||||||
isinstance(content_type, str)
|
isinstance(content_type, str)
|
||||||
and content_type.startswith(self.MIME_TYPE)
|
and content_type.startswith(self.MIME_TYPE)
|
||||||
) or ("ce-specversion" not in headers)
|
or not has_binary_headers(headers)
|
||||||
|
)
|
||||||
|
|
||||||
def event_supported(self, event: object) -> bool:
|
def event_supported(self, event: object) -> bool:
|
||||||
# structured format supported by both spec 0.1 and 0.2
|
# structured format supported by both spec 0.1 and 0.2
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
def has_binary_headers(headers: typing.Dict[str, str]) -> bool:
|
||||||
|
return (
|
||||||
|
"ce-specversion" in headers
|
||||||
|
and "ce-source" in headers
|
||||||
|
and "ce-type" in headers
|
||||||
|
and "ce-id" in headers
|
||||||
|
)
|
||||||
|
|
@ -16,10 +16,12 @@ import base64
|
||||||
import json
|
import json
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import cloudevents.exceptions as cloud_exceptions
|
||||||
from cloudevents.sdk import types
|
from cloudevents.sdk import types
|
||||||
|
|
||||||
|
|
||||||
# TODO(slinkydeveloper) is this really needed?
|
# TODO(slinkydeveloper) is this really needed?
|
||||||
|
|
||||||
|
|
||||||
class EventGetterSetter(object):
|
class EventGetterSetter(object):
|
||||||
|
|
||||||
# ce-specversion
|
# ce-specversion
|
||||||
|
|
@ -159,6 +161,9 @@ class EventGetterSetter(object):
|
||||||
|
|
||||||
|
|
||||||
class BaseEvent(EventGetterSetter):
|
class BaseEvent(EventGetterSetter):
|
||||||
|
_ce_required_fields = set()
|
||||||
|
_ce_optional_fields = set()
|
||||||
|
|
||||||
def Properties(self, with_nullable=False) -> dict:
|
def Properties(self, with_nullable=False) -> dict:
|
||||||
props = dict()
|
props = dict()
|
||||||
for name, value in self.__dict__.items():
|
for name, value in self.__dict__.items():
|
||||||
|
|
@ -215,7 +220,9 @@ class BaseEvent(EventGetterSetter):
|
||||||
|
|
||||||
missing_fields = self._ce_required_fields - raw_ce.keys()
|
missing_fields = self._ce_required_fields - raw_ce.keys()
|
||||||
if len(missing_fields) > 0:
|
if len(missing_fields) > 0:
|
||||||
raise ValueError(f"Missing required attributes: {missing_fields}")
|
raise cloud_exceptions.CloudEventMissingRequiredFields(
|
||||||
|
f"Missing required attributes: {missing_fields}"
|
||||||
|
)
|
||||||
|
|
||||||
for name, value in raw_ce.items():
|
for name, value in raw_ce.items():
|
||||||
if name == "data":
|
if name == "data":
|
||||||
|
|
@ -233,8 +240,16 @@ class BaseEvent(EventGetterSetter):
|
||||||
body: typing.Union[bytes, str],
|
body: typing.Union[bytes, str],
|
||||||
data_unmarshaller: types.UnmarshallerType,
|
data_unmarshaller: types.UnmarshallerType,
|
||||||
):
|
):
|
||||||
if "ce-specversion" not in headers:
|
required_binary_fields = {
|
||||||
raise ValueError("Missing required attribute: 'specversion'")
|
f"ce-{field}" for field in self._ce_required_fields
|
||||||
|
}
|
||||||
|
missing_fields = required_binary_fields - headers.keys()
|
||||||
|
|
||||||
|
if len(missing_fields) > 0:
|
||||||
|
raise cloud_exceptions.CloudEventMissingRequiredFields(
|
||||||
|
f"Missing required attributes: {missing_fields}"
|
||||||
|
)
|
||||||
|
|
||||||
for header, value in headers.items():
|
for header, value in headers.items():
|
||||||
header = header.lower()
|
header = header.lower()
|
||||||
if header == "content-type":
|
if header == "content-type":
|
||||||
|
|
@ -242,9 +257,6 @@ class BaseEvent(EventGetterSetter):
|
||||||
elif header.startswith("ce-"):
|
elif header.startswith("ce-"):
|
||||||
self.Set(header[3:], value)
|
self.Set(header[3:], value)
|
||||||
self.Set("data", data_unmarshaller(body))
|
self.Set("data", data_unmarshaller(body))
|
||||||
missing_attrs = self._ce_required_fields - self.Properties().keys()
|
|
||||||
if len(missing_attrs) > 0:
|
|
||||||
raise ValueError(f"Missing required attributes: {missing_attrs}")
|
|
||||||
|
|
||||||
def MarshalBinary(
|
def MarshalBinary(
|
||||||
self, data_marshaller: types.MarshallerType
|
self, data_marshaller: types.MarshallerType
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from cloudevents.http import CloudEvent
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("specversion", ["0.3", "1.0"])
|
||||||
|
def test_http_cloudevent_equality(specversion):
|
||||||
|
attributes = {
|
||||||
|
"source": "<source>",
|
||||||
|
"specversion": specversion,
|
||||||
|
"id": "my-id",
|
||||||
|
"time": "tomorrow",
|
||||||
|
"type": "tests.cloudevents.override",
|
||||||
|
"datacontenttype": "application/json",
|
||||||
|
"subject": "my-subject",
|
||||||
|
}
|
||||||
|
data = '{"name":"john"}'
|
||||||
|
event1 = CloudEvent(attributes, data)
|
||||||
|
event2 = CloudEvent(attributes, data)
|
||||||
|
assert event1 == event2
|
||||||
|
# Test different attributes
|
||||||
|
for key in attributes:
|
||||||
|
if key == "specversion":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
attributes[key] = f"noise-{key}"
|
||||||
|
event3 = CloudEvent(attributes, data)
|
||||||
|
event2 = CloudEvent(attributes, data)
|
||||||
|
assert event2 == event3
|
||||||
|
assert event1 != event2 and event3 != event1
|
||||||
|
|
||||||
|
# Test different data
|
||||||
|
data = '{"name":"paul"}'
|
||||||
|
event3 = CloudEvent(attributes, data)
|
||||||
|
event2 = CloudEvent(attributes, data)
|
||||||
|
assert event2 == event3
|
||||||
|
assert event1 != event2 and event3 != event1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("specversion", ["0.3", "1.0"])
|
||||||
|
def test_http_cloudevent_mutates_equality(specversion):
|
||||||
|
attributes = {
|
||||||
|
"source": "<source>",
|
||||||
|
"specversion": specversion,
|
||||||
|
"id": "my-id",
|
||||||
|
"time": "tomorrow",
|
||||||
|
"type": "tests.cloudevents.override",
|
||||||
|
"datacontenttype": "application/json",
|
||||||
|
"subject": "my-subject",
|
||||||
|
}
|
||||||
|
data = '{"name":"john"}'
|
||||||
|
event1 = CloudEvent(attributes, data)
|
||||||
|
event2 = CloudEvent(attributes, data)
|
||||||
|
event3 = CloudEvent(attributes, data)
|
||||||
|
|
||||||
|
assert event1 == event2
|
||||||
|
# Test different attributes
|
||||||
|
for key in attributes:
|
||||||
|
if key == "specversion":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
event2[key] = f"noise-{key}"
|
||||||
|
event3[key] = f"noise-{key}"
|
||||||
|
assert event2 == event3
|
||||||
|
assert event1 != event2 and event3 != event1
|
||||||
|
|
||||||
|
# Test different data
|
||||||
|
event2.data = '{"name":"paul"}'
|
||||||
|
event3.data = '{"name":"paul"}'
|
||||||
|
assert event2 == event3
|
||||||
|
assert event1 != event2 and event3 != event1
|
||||||
|
|
@ -20,9 +20,11 @@ import json
|
||||||
import pytest
|
import pytest
|
||||||
from sanic import Sanic, response
|
from sanic import Sanic, response
|
||||||
|
|
||||||
|
import cloudevents.exceptions as cloud_exceptions
|
||||||
from cloudevents.http import (
|
from cloudevents.http import (
|
||||||
CloudEvent,
|
CloudEvent,
|
||||||
from_http,
|
from_http,
|
||||||
|
is_binary,
|
||||||
to_binary_http,
|
to_binary_http,
|
||||||
to_structured_http,
|
to_structured_http,
|
||||||
)
|
)
|
||||||
|
|
@ -47,7 +49,7 @@ invalid_test_headers = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
invalid_cloudevent_request_bodie = [
|
invalid_cloudevent_request_body = [
|
||||||
{
|
{
|
||||||
"source": "<event-source>",
|
"source": "<event-source>",
|
||||||
"type": "cloudevent.event.type",
|
"type": "cloudevent.event.type",
|
||||||
|
|
@ -87,21 +89,22 @@ async def echo(request):
|
||||||
return response.raw(data, headers={k: event[k] for k in event})
|
return response.raw(data, headers={k: event[k] for k in event})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("body", invalid_cloudevent_request_bodie)
|
@pytest.mark.parametrize("body", invalid_cloudevent_request_body)
|
||||||
def test_missing_required_fields_structured(body):
|
def test_missing_required_fields_structured(body):
|
||||||
with pytest.raises((TypeError, NotImplementedError)):
|
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
|
||||||
# CloudEvent constructor throws TypeError if missing required field
|
# CloudEvent constructor throws TypeError if missing required field
|
||||||
# and NotImplementedError because structured calls aren't
|
# and NotImplementedError because structured calls aren't
|
||||||
# implemented. In this instance one of the required keys should have
|
# implemented. In this instance one of the required keys should have
|
||||||
# prefix e-id instead of ce-id therefore it should throw
|
# prefix e-id instead of ce-id therefore it should throw
|
||||||
_ = from_http(
|
_ = from_http(
|
||||||
json.dumps(body), attributes={"Content-Type": "application/json"}
|
json.dumps(body),
|
||||||
|
headers={"Content-Type": "application/cloudevents+json"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("headers", invalid_test_headers)
|
@pytest.mark.parametrize("headers", invalid_test_headers)
|
||||||
def test_missing_required_fields_binary(headers):
|
def test_missing_required_fields_binary(headers):
|
||||||
with pytest.raises((ValueError)):
|
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
|
||||||
# CloudEvent constructor throws TypeError if missing required field
|
# CloudEvent constructor throws TypeError if missing required field
|
||||||
# and NotImplementedError because structured calls aren't
|
# and NotImplementedError because structured calls aren't
|
||||||
# implemented. In this instance one of the required keys should have
|
# implemented. In this instance one of the required keys should have
|
||||||
|
|
@ -165,7 +168,7 @@ def test_emit_structured_event(specversion):
|
||||||
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
||||||
def test_roundtrip_non_json_event(converter, specversion):
|
def test_roundtrip_non_json_event(converter, specversion):
|
||||||
input_data = io.BytesIO()
|
input_data = io.BytesIO()
|
||||||
for i in range(100):
|
for _ in range(100):
|
||||||
for j in range(20):
|
for j in range(20):
|
||||||
assert 1 == input_data.write(j.to_bytes(1, byteorder="big"))
|
assert 1 == input_data.write(j.to_bytes(1, byteorder="big"))
|
||||||
compressed_data = bz2.compress(input_data.getvalue())
|
compressed_data = bz2.compress(input_data.getvalue())
|
||||||
|
|
@ -201,7 +204,7 @@ def test_missing_ce_prefix_binary_event(specversion):
|
||||||
# breaking prefix e.g. e-id instead of ce-id
|
# breaking prefix e.g. e-id instead of ce-id
|
||||||
prefixed_headers[key[1:]] = headers[key]
|
prefixed_headers[key[1:]] = headers[key]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(cloud_exceptions.CloudEventMissingRequiredFields):
|
||||||
# CloudEvent constructor throws TypeError if missing required field
|
# CloudEvent constructor throws TypeError if missing required field
|
||||||
# and NotImplementedError because structured calls aren't
|
# and NotImplementedError because structured calls aren't
|
||||||
# implemented. In this instance one of the required keys should have
|
# implemented. In this instance one of the required keys should have
|
||||||
|
|
@ -278,7 +281,7 @@ def test_empty_data_structured_event(specversion):
|
||||||
# Testing if cloudevent breaks when no structured data field present
|
# Testing if cloudevent breaks when no structured data field present
|
||||||
attributes = {
|
attributes = {
|
||||||
"specversion": specversion,
|
"specversion": specversion,
|
||||||
"datacontenttype": "application/json",
|
"datacontenttype": "application/cloudevents+json",
|
||||||
"type": "word.found.name",
|
"type": "word.found.name",
|
||||||
"id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
|
"id": "96fb5f0b-001e-0108-6dfe-da6e2806f124",
|
||||||
"time": "2018-10-23T12:28:22.4579346Z",
|
"time": "2018-10-23T12:28:22.4579346Z",
|
||||||
|
|
@ -308,7 +311,6 @@ def test_empty_data_binary_event(specversion):
|
||||||
def test_valid_structured_events(specversion):
|
def test_valid_structured_events(specversion):
|
||||||
# Test creating multiple cloud events
|
# Test creating multiple cloud events
|
||||||
events_queue = []
|
events_queue = []
|
||||||
headers = {}
|
|
||||||
num_cloudevents = 30
|
num_cloudevents = 30
|
||||||
for i in range(num_cloudevents):
|
for i in range(num_cloudevents):
|
||||||
event = {
|
event = {
|
||||||
|
|
@ -335,9 +337,6 @@ def test_valid_structured_events(specversion):
|
||||||
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
||||||
def test_structured_no_content_type(specversion):
|
def test_structured_no_content_type(specversion):
|
||||||
# Test creating multiple cloud events
|
# Test creating multiple cloud events
|
||||||
events_queue = []
|
|
||||||
headers = {}
|
|
||||||
num_cloudevents = 30
|
|
||||||
data = {
|
data = {
|
||||||
"id": "id",
|
"id": "id",
|
||||||
"source": "source.com.test",
|
"source": "source.com.test",
|
||||||
|
|
@ -362,28 +361,15 @@ def test_is_binary():
|
||||||
"ce-specversion": "1.0",
|
"ce-specversion": "1.0",
|
||||||
"Content-Type": "text/plain",
|
"Content-Type": "text/plain",
|
||||||
}
|
}
|
||||||
assert converters.is_binary(headers)
|
assert is_binary(headers)
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/cloudevents+json",
|
"Content-Type": "application/cloudevents+json",
|
||||||
}
|
}
|
||||||
assert not converters.is_binary(headers)
|
assert not is_binary(headers)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
assert not converters.is_binary(headers)
|
assert not is_binary(headers)
|
||||||
|
|
||||||
|
|
||||||
def test_is_structured():
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/cloudevents+json",
|
|
||||||
}
|
|
||||||
assert converters.is_structured(headers)
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
assert converters.is_structured(headers)
|
|
||||||
|
|
||||||
headers = {"ce-specversion": "1.0"}
|
|
||||||
assert not converters.is_structured(headers)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
@pytest.mark.parametrize("specversion", ["1.0", "0.3"])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue