diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..39e7196a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem + +1. +2. +3. + +## Specifications + +- Platform: +- Python Version: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 626357dd..75a0e8ff 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,10 @@ -- Link to issue this resolves +Fixes # -- What I did +## Changes -- How I did it -- How to verify it +## One line description for the changelog -- One line description for the changelog + +- [ ] Tests pass +- [ ] Appropriate changes to README are included in PR diff --git a/README.md b/README.md index df5d7b28..5e392270 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ This SDK is still considered a work in progress, therefore things might (and will) break with every update. This SDK current supports the following versions of CloudEvents: + - v1.0 - v0.3 -- v0.2 -- v0.1 ## Python SDK @@ -116,7 +115,6 @@ In this topic you'd find various example how to integrate an SDK with various HT One of popular framework is [`requests`](http://docs.python-requests.org/en/master/). - #### CloudEvent to request The code below shows how integrate both libraries in order to convert a CloudEvent into an HTTP request: @@ -155,27 +153,29 @@ Complete example of turning a CloudEvent into a request you can find [here](samp #### Request to CloudEvent The code below shows how integrate both libraries in order to create a CloudEvent from an HTTP request: + ```python response = requests.get(url) response.raise_for_status() headers = response.headers data = io.BytesIO(response.content) - event = v02.Event() + event = v1.Event() http_marshaller = marshaller.NewDefaultHTTPMarshaller() event = http_marshaller.FromRequest( event, headers, data, json.load) ``` -Complete example of turning a CloudEvent into a request you can find [here](samples/python-requests/request_to_cloudevent.py). +Complete example of turning a CloudEvent into a request you can find [here](samples/python-requests/request_to_cloudevent.py). ## SDK versioning The goal of this package is to provide support for all released versions of CloudEvents, ideally while maintaining the same API. It will use semantic versioning with following rules: -* MAJOR version increments when backwards incompatible changes is introduced. -* MINOR version increments when backwards compatible feature is introduced INCLUDING support for new CloudEvents version. -* PATCH version increments when a backwards compatible bug fix is introduced. + +- MAJOR version increments when backwards incompatible changes is introduced. +- MINOR version increments when backwards compatible feature is introduced INCLUDING support for new CloudEvents version. +- PATCH version increments when a backwards compatible bug fix is introduced. ## Community diff --git a/cloudevents/sdk/converters/binary.py b/cloudevents/sdk/converters/binary.py index 97c4e440..7bc0025e 100644 --- a/cloudevents/sdk/converters/binary.py +++ b/cloudevents/sdk/converters/binary.py @@ -17,13 +17,13 @@ from cloudevents.sdk import exceptions from cloudevents.sdk.converters import base from cloudevents.sdk.event import base as event_base -from cloudevents.sdk.event import v02, v03, v1 +from cloudevents.sdk.event import v03, v1 class BinaryHTTPCloudEventConverter(base.Converter): TYPE = "binary" - SUPPORTED_VERSIONS = [v02.Event, v03.Event, v1.Event] + SUPPORTED_VERSIONS = [v03.Event, v1.Event] def can_read(self, content_type: str) -> bool: return True diff --git a/cloudevents/sdk/event/v01.py b/cloudevents/sdk/event/v01.py deleted file mode 100644 index 5192d8f8..00000000 --- a/cloudevents/sdk/event/v01.py +++ /dev/null @@ -1,137 +0,0 @@ -# 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. - -from cloudevents.sdk.event import base -from cloudevents.sdk.event import opt - - -class Event(base.BaseEvent): - def __init__(self): - self.ce__cloudEventsVersion = opt.Option( - "cloudEventsVersion", - "0.1", - True - ) - self.ce__eventType = opt.Option( - "eventType", - None, - True - ) - self.ce__eventTypeVersion = opt.Option( - "eventTypeVersion", - None, - False - ) - self.ce__source = opt.Option( - "source", - None, - True - ) - self.ce__eventID = opt.Option( - "eventID", - None, - True - ) - self.ce__eventTime = opt.Option( - "eventTime", - None, - True - ) - self.ce__schemaURL = opt.Option( - "schemaURL", - None, - False - ) - self.ce__contentType = opt.Option( - "contentType", - None, - False - ) - self.ce__data = opt.Option( - "data", - None, - False - ) - self.ce__extensions = opt.Option( - "extensions", - dict(), - False - ) - - def CloudEventVersion(self) -> str: - return self.ce__cloudEventsVersion.get() - - def EventType(self) -> str: - return self.ce__eventType.get() - - def Source(self) -> str: - return self.ce__source.get() - - def EventID(self) -> str: - return self.ce__eventID.get() - - def EventTime(self) -> str: - return self.ce__eventTime.get() - - def SchemaURL(self) -> str: - return self.ce__schemaURL.get() - - def Data(self) -> object: - return self.ce__data.get() - - def Extensions(self) -> dict: - return self.ce__extensions.get() - - def ContentType(self) -> str: - return self.ce__contentType.get() - - def SetEventType(self, eventType: str) -> base.BaseEvent: - self.Set("eventType", eventType) - return self - - def SetSource(self, source: str) -> base.BaseEvent: - self.Set("source", source) - return self - - def SetEventID(self, eventID: str) -> base.BaseEvent: - self.Set("eventID", eventID) - return self - - def SetEventTime(self, eventTime: str) -> base.BaseEvent: - self.Set("eventTime", eventTime) - return self - - def SetSchemaURL(self, schemaURL: str) -> base.BaseEvent: - self.Set("schemaURL", schemaURL) - return self - - def SetData(self, data: object) -> base.BaseEvent: - self.Set("data", data) - return self - - def SetExtensions(self, extensions: dict) -> base.BaseEvent: - self.Set("extensions", extensions) - return self - - def SetContentType(self, contentType: str) -> base.BaseEvent: - self.Set("contentType", contentType) - return self - - # additional getter/setter - def EventTypeVersion(self) -> str: - return self.ce__eventTypeVersion.get() - - def WithEventTypeVersion(self, eventTypeVersion: str) -> base.BaseEvent: - self.Set("eventTypeVersion", eventTypeVersion) - return self diff --git a/cloudevents/sdk/event/v02.py b/cloudevents/sdk/event/v02.py deleted file mode 100644 index f2da7929..00000000 --- a/cloudevents/sdk/event/v02.py +++ /dev/null @@ -1,88 +0,0 @@ -# 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. - -from cloudevents.sdk.event import base -from cloudevents.sdk.event import opt - - -class Event(base.BaseEvent): - def __init__(self): - self.ce__specversion = opt.Option("specversion", "0.2", True) - self.ce__type = opt.Option("type", None, True) - self.ce__source = opt.Option("source", None, True) - self.ce__id = opt.Option("id", None, True) - self.ce__time = opt.Option("time", None, True) - self.ce__schemaurl = opt.Option("schemaurl", None, False) - self.ce__contenttype = opt.Option("contenttype", None, False) - self.ce__data = opt.Option("data", None, False) - self.ce__extensions = opt.Option("extensions", dict(), False) - - def CloudEventVersion(self) -> str: - return self.ce__specversion.get() - - def EventType(self) -> str: - return self.ce__type.get() - - def Source(self) -> str: - return self.ce__source.get() - - def EventID(self) -> str: - return self.ce__id.get() - - def EventTime(self) -> str: - return self.ce__time.get() - - def SchemaURL(self) -> str: - return self.ce__schemaurl.get() - - def Data(self) -> object: - return self.ce__data.get() - - def Extensions(self) -> dict: - return self.ce__extensions.get() - - def ContentType(self) -> str: - return self.ce__contenttype.get() - - def SetEventType(self, eventType: str) -> base.BaseEvent: - self.Set("type", eventType) - return self - - def SetSource(self, source: str) -> base.BaseEvent: - self.Set("source", source) - return self - - def SetEventID(self, eventID: str) -> base.BaseEvent: - self.Set("id", eventID) - return self - - def SetEventTime(self, eventTime: str) -> base.BaseEvent: - self.Set("time", eventTime) - return self - - def SetSchemaURL(self, schemaURL: str) -> base.BaseEvent: - self.Set("schemaurl", schemaURL) - return self - - def SetData(self, data: object) -> base.BaseEvent: - self.Set("data", data) - return self - - def SetExtensions(self, extensions: dict) -> base.BaseEvent: - self.Set("extensions", extensions) - return self - - def SetContentType(self, contentType: str) -> base.BaseEvent: - self.Set("contenttype", contentType) - return self diff --git a/cloudevents/tests/data.py b/cloudevents/tests/data.py index f4110c93..ffe63aee 100644 --- a/cloudevents/tests/data.py +++ b/cloudevents/tests/data.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from cloudevents.sdk.event import v02, v03, v1 +from cloudevents.sdk.event import v03, v1 contentType = "application/json" ce_type = "word.found.exclamation" @@ -22,14 +22,6 @@ body = '{"name":"john"}' headers = { - v02.Event: { - "ce-specversion": "1.0", - "ce-type": ce_type, - "ce-id": ce_id, - "ce-time": eventTime, - "ce-source": source, - "Content-Type": contentType, - }, v03.Event: { "ce-specversion": "1.0", "ce-type": ce_type, @@ -49,14 +41,6 @@ } json_ce = { - v02.Event: { - "specversion": "1.0", - "type": ce_type, - "id": ce_id, - "time": eventTime, - "source": source, - "contenttype": contentType, - }, v03.Event: { "specversion": "1.0", "type": ce_type, diff --git a/cloudevents/tests/test_event_from_request_converter.py b/cloudevents/tests/test_event_from_request_converter.py index 76930c5e..65a89703 100644 --- a/cloudevents/tests/test_event_from_request_converter.py +++ b/cloudevents/tests/test_event_from_request_converter.py @@ -19,8 +19,6 @@ from cloudevents.sdk import exceptions from cloudevents.sdk import marshaller -from cloudevents.sdk.event import v01 -from cloudevents.sdk.event import v02 from cloudevents.sdk.event import v03 from cloudevents.sdk.event import v1 @@ -30,18 +28,23 @@ from cloudevents.tests import data -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_binary_converter_upstream(event_class): m = marshaller.NewHTTPMarshaller( [binary.NewBinaryHTTPCloudEventConverter()]) - event = m.FromRequest(event_class(), data.headers[event_class], None, lambda x: x) + event = m.FromRequest( + event_class(), + data.headers[event_class], + None, + lambda x: x + ) assert event is not None assert event.EventType() == data.ce_type assert event.EventID() == data.ce_id assert event.ContentType() == data.contentType -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_structured_converter_upstream(event_class): m = marshaller.NewHTTPMarshaller( [structured.NewJSONHTTPCloudEventConverter()]) @@ -58,49 +61,7 @@ def test_structured_converter_upstream(event_class): assert event.ContentType() == data.contentType -def test_binary_converter_v01(): - m = marshaller.NewHTTPMarshaller( - [binary.NewBinaryHTTPCloudEventConverter()]) - - pytest.raises( - exceptions.UnsupportedEventConverter, - m.FromRequest, - v01.Event, - {}, - None, - lambda x: x, - ) - - -def test_unsupported_converter_v01(): - m = marshaller.NewHTTPMarshaller( - [structured.NewJSONHTTPCloudEventConverter()]) - - pytest.raises( - exceptions.UnsupportedEventConverter, - m.FromRequest, - v01.Event, - {}, - None, - lambda x: x, - ) - - -def test_structured_converter_v01(): - m = marshaller.NewHTTPMarshaller( - [structured.NewJSONHTTPCloudEventConverter()]) - event = m.FromRequest( - v01.Event(), - {"Content-Type": "application/cloudevents+json"}, - io.StringIO(json.dumps(data.json_ce[v02.Event])), - lambda x: x.read(), - ) - - assert event is not None - assert event.Get("type") == (data.ce_type, True) - assert event.Get("id") == (data.ce_id, True) - -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_default_http_marshaller_with_structured(event_class): m = marshaller.NewDefaultHTTPMarshaller() @@ -116,7 +77,7 @@ def test_default_http_marshaller_with_structured(event_class): assert event.ContentType() == data.contentType -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_default_http_marshaller_with_binary(event_class): m = marshaller.NewDefaultHTTPMarshaller() @@ -130,32 +91,3 @@ def test_default_http_marshaller_with_binary(event_class): assert event.EventID() == data.ce_id assert event.ContentType() == data.contentType assert event.Data() == data.body - - -def test_unsupported_event_configuration(): - m = marshaller.NewHTTPMarshaller( - [binary.NewBinaryHTTPCloudEventConverter()]) - pytest.raises( - exceptions.UnsupportedEventConverter, - m.FromRequest, - v01.Event(), - {"Content-Type": "application/cloudevents+json"}, - io.StringIO(json.dumps(data.json_ce[v02.Event])), - lambda x: x.read(), - ) - - -def test_invalid_data_unmarshaller(): - m = marshaller.NewDefaultHTTPMarshaller() - pytest.raises( - exceptions.InvalidDataUnmarshaller, - m.FromRequest, - v01.Event(), {}, None, None - ) - - -def test_invalid_data_marshaller(): - m = marshaller.NewDefaultHTTPMarshaller() - pytest.raises( - exceptions.InvalidDataMarshaller, m.ToRequest, v01.Event(), "blah", None - ) diff --git a/cloudevents/tests/test_event_pipeline.py b/cloudevents/tests/test_event_pipeline.py index 554d8b29..09f029b2 100644 --- a/cloudevents/tests/test_event_pipeline.py +++ b/cloudevents/tests/test_event_pipeline.py @@ -16,7 +16,7 @@ import json import pytest -from cloudevents.sdk.event import v01, v02, v03, v1 +from cloudevents.sdk.event import v03, v1 from cloudevents.sdk import converters from cloudevents.sdk import marshaller @@ -24,7 +24,8 @@ from cloudevents.tests import data -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) + +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_event_pipeline_upstream(event_class): event = ( event_class() @@ -52,37 +53,12 @@ def test_event_pipeline_upstream(event_class): def test_extensions_are_set_upstream(): extensions = {'extension-key': 'extension-value'} event = ( - v02.Event() + v1.Event() .SetExtensions(extensions) ) m = marshaller.NewDefaultHTTPMarshaller() - new_headers, body = m.ToRequest(event, converters.TypeBinary, lambda x: x) + new_headers, _ = m.ToRequest(event, converters.TypeBinary, lambda x: x) assert event.Extensions() == extensions assert "ce-extension-key" in new_headers - - -def test_event_pipeline_v01(): - event = ( - v01.Event() - .SetContentType(data.contentType) - .SetData(data.body) - .SetEventID(data.ce_id) - .SetSource(data.source) - .SetEventTime(data.eventTime) - .SetEventType(data.ce_type) - ) - m = marshaller.NewHTTPMarshaller([structured.NewJSONHTTPCloudEventConverter()]) - - _, body = m.ToRequest(event, converters.TypeStructured, lambda x: x) - assert isinstance(body, io.BytesIO) - new_headers = json.load(io.TextIOWrapper(body, encoding="utf-8")) - assert new_headers is not None - assert "cloudEventsVersion" in new_headers - assert "eventType" in new_headers - assert "source" in new_headers - assert "eventID" in new_headers - assert "eventTime" in new_headers - assert "contentType" in new_headers - assert data.body == new_headers["data"] diff --git a/cloudevents/tests/test_event_to_request_converter.py b/cloudevents/tests/test_event_to_request_converter.py index 0719035f..06f2e679 100644 --- a/cloudevents/tests/test_event_to_request_converter.py +++ b/cloudevents/tests/test_event_to_request_converter.py @@ -21,14 +21,12 @@ from cloudevents.sdk import marshaller from cloudevents.sdk.converters import structured -from cloudevents.sdk.event import v01, v02, v03, v1 -from cloudevents.sdk.event import v02 - +from cloudevents.sdk.event import v03, v1 from cloudevents.tests import data -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_binary_event_to_request_upstream(event_class): m = marshaller.NewDefaultHTTPMarshaller() event = m.FromRequest( @@ -48,13 +46,18 @@ def test_binary_event_to_request_upstream(event_class): assert "ce-specversion" in new_headers -@pytest.mark.parametrize("event_class", [v02.Event, v03.Event, v1.Event]) +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) def test_structured_event_to_request_upstream(event_class): copy_of_ce = copy.deepcopy(data.json_ce[event_class]) m = marshaller.NewDefaultHTTPMarshaller() http_headers = {"content-type": "application/cloudevents+json"} event = m.FromRequest( - event_class(), http_headers, io.StringIO(json.dumps(data.json_ce[event_class])), lambda x: x.read() + event_class(), + http_headers, + io.StringIO( + json.dumps(data.json_ce[event_class]) + ), + lambda x: x.read() ) assert event is not None assert event.EventType() == data.ce_type @@ -67,22 +70,3 @@ def test_structured_event_to_request_upstream(event_class): assert new_headers[key] == http_headers[key] continue assert key in copy_of_ce - - -def test_structured_event_to_request_v01(): - copy_of_ce = copy.deepcopy(data.json_ce[v02.Event]) - m = marshaller.NewHTTPMarshaller([structured.NewJSONHTTPCloudEventConverter()]) - http_headers = {"content-type": "application/cloudevents+json"} - event = m.FromRequest( - v01.Event(), http_headers, io.StringIO(json.dumps(data.json_ce[v02.Event])), lambda x: x.read() - ) - assert event is not None - assert event.Get("type") == (data.ce_type, True) - assert event.Get("id") == (data.ce_id, True) - - new_headers, _ = m.ToRequest(event, converters.TypeStructured, lambda x: x) - for key in new_headers: - if key == "content-type": - assert new_headers[key] == http_headers[key] - continue - assert key in copy_of_ce diff --git a/cloudevents/tests/test_with_sanic.py b/cloudevents/tests/test_with_sanic.py index ca6f68e8..2fd99337 100644 --- a/cloudevents/tests/test_with_sanic.py +++ b/cloudevents/tests/test_with_sanic.py @@ -14,7 +14,7 @@ from cloudevents.sdk import marshaller from cloudevents.sdk import converters -from cloudevents.sdk.event import v02 +from cloudevents.sdk.event import v1 from sanic import Sanic from sanic import response @@ -29,7 +29,7 @@ @app.route("/is-ok", ["POST"]) async def is_ok(request): m.FromRequest( - v02.Event(), + v1.Event(), dict(request.headers), request.body, lambda x: x @@ -40,7 +40,7 @@ async def is_ok(request): @app.route("/echo", ["POST"]) async def echo(request): event = m.FromRequest( - v02.Event(), + v1.Event(), dict(request.headers), request.body, lambda x: x @@ -50,28 +50,29 @@ async def echo(request): def test_reusable_marshaller(): - for i in range(10): + for _ in range(10): _, r = app.test_client.post( - "/is-ok", headers=test_data.headers[v02.Event], data=test_data.body + "/is-ok", headers=test_data.headers[v1.Event], data=test_data.body ) assert r.status == 200 def test_web_app_integration(): _, r = app.test_client.post( - "/is-ok", headers=test_data.headers[v02.Event], data=test_data.body + "/is-ok", headers=test_data.headers[v1.Event], data=test_data.body ) assert r.status == 200 def test_web_app_echo(): - _, r = app.test_client.post("/echo", headers=test_data.headers[v02.Event], data=test_data.body) + _, r = app.test_client.post( + "/echo", headers=test_data.headers[v1.Event], data=test_data.body) assert r.status == 200 - event = m.FromRequest(v02.Event(), dict(r.headers), r.body, lambda x: x) + event = m.FromRequest(v1.Event(), dict(r.headers), r.body, lambda x: x) assert event is not None props = event.Properties() - for key in test_data.headers[v02.Event].keys(): + for key in test_data.headers[v1.Event].keys(): if key == "Content-Type": - assert "contenttype" in props + assert "datacontenttype" in props else: assert key.lstrip("ce-") in props diff --git a/samples/python-requests/cloudevent_to_request.py b/samples/python-requests/cloudevent_to_request.py index 4b0f4acf..0ae1d113 100644 --- a/samples/python-requests/cloudevent_to_request.py +++ b/samples/python-requests/cloudevent_to_request.py @@ -19,7 +19,7 @@ from cloudevents.sdk import converters from cloudevents.sdk import marshaller -from cloudevents.sdk.event import v02 +from cloudevents.sdk.event import v1 def run_binary(event, url): @@ -60,7 +60,7 @@ def run_structured(event, url): http_marshaller = marshaller.NewDefaultHTTPMarshaller() event = ( - v02.Event(). + v1.Event(). SetContentType("application/json"). SetData({"name": "denis"}). SetEventID("my-id"). diff --git a/samples/python-requests/request_to_cloudevent.py b/samples/python-requests/request_to_cloudevent.py index 11d3cc72..0ec7e8d2 100644 --- a/samples/python-requests/request_to_cloudevent.py +++ b/samples/python-requests/request_to_cloudevent.py @@ -19,7 +19,7 @@ from cloudevents.sdk import marshaller -from cloudevents.sdk.event import v02 +from cloudevents.sdk.event import v1 if __name__ == "__main__": @@ -33,7 +33,7 @@ response.raise_for_status() headers = response.headers data = io.BytesIO(response.content) - event = v02.Event() + event = v1.Event() http_marshaller = marshaller.NewDefaultHTTPMarshaller() event = http_marshaller.FromRequest( event, headers, data, json.load)