diff --git a/cloudevents/sdk/event/base.py b/cloudevents/sdk/event/base.py index a8bb099e..4ad32a5d 100644 --- a/cloudevents/sdk/event/base.py +++ b/cloudevents/sdk/event/base.py @@ -35,61 +35,141 @@ # TODO(slinkydeveloper) is this really needed? class EventGetterSetter(object): + # ce-specversion def CloudEventVersion(self) -> str: raise Exception("not implemented") - # CloudEvent attribute getters - def EventType(self) -> str: - raise Exception("not implemented") + @property + def specversion(self): + return self.CloudEventVersion() - def Source(self) -> str: + def SetCloudEventVersion(self, specversion: str) -> object: raise Exception("not implemented") - def EventID(self) -> str: - raise Exception("not implemented") + @specversion.setter + def specversion(self, value: str): + self.SetCloudEventVersion(value) - def EventTime(self) -> str: + # ce-type + def EventType(self) -> str: raise Exception("not implemented") - def SchemaURL(self) -> str: - raise Exception("not implemented") + @property + def type(self): + return self.EventType() - def Data(self) -> object: + def SetEventType(self, eventType: str) -> object: raise Exception("not implemented") - def Extensions(self) -> dict: - raise Exception("not implemented") + @type.setter + def type(self, value: str): + self.SetEventType(value) - def ContentType(self) -> str: + # ce-source + def Source(self) -> str: raise Exception("not implemented") - # CloudEvent attribute constructors - # Each setter return an instance of its class - # in order to build a pipeline of setter - def SetEventType(self, eventType: str) -> object: - raise Exception("not implemented") + @property + def source(self): + return self.Source() def SetSource(self, source: str) -> object: raise Exception("not implemented") + @source.setter + def source(self, value: str): + self.SetSource(value) + + # ce-id + def EventID(self) -> str: + raise Exception("not implemented") + + @property + def id(self): + return self.EventID() + def SetEventID(self, eventID: str) -> object: raise Exception("not implemented") + @id.setter + def id(self, value: str): + self.SetEventID(value) + + # ce-time + def EventTime(self) -> str: + raise Exception("not implemented") + + @property + def time(self): + return self.EventTime() + def SetEventTime(self, eventTime: str) -> object: raise Exception("not implemented") + @time.setter + def time(self, value: str): + self.SetEventTime(value) + + # ce-schema + def SchemaURL(self) -> str: + raise Exception("not implemented") + + @property + def schema(self) -> str: + return self.SchemaURL() + def SetSchemaURL(self, schemaURL: str) -> object: raise Exception("not implemented") + @schema.setter + def schema(self, value: str): + self.SetSchemaURL(value) + + # data + def Data(self) -> object: + raise Exception("not implemented") + + @property + def data(self) -> object: + return self.Data() + def SetData(self, data: object) -> object: raise Exception("not implemented") + @data.setter + def data(self, value: object): + self.SetData(value) + + # ce-extensions + def Extensions(self) -> dict: + raise Exception("not implemented") + + @property + def extensions(self) -> dict: + return self.Extensions() + def SetExtensions(self, extensions: dict) -> object: raise Exception("not implemented") + @extensions.setter + def extensions(self, value: dict): + self.SetExtensions(value) + + # Content-Type + def ContentType(self) -> str: + raise Exception("not implemented") + + @property + def content_type(self) -> str: + return self.ContentType() + def SetContentType(self, contentType: str) -> object: raise Exception("not implemented") + @content_type.setter + def content_type(self, value: str): + self.SetContentType(value) + class BaseEvent(EventGetterSetter): def Properties(self, with_nullable=False) -> dict: @@ -120,7 +200,6 @@ def Set(self, key: str, value: object): attr.set(value) setattr(self, formatted_key, attr) return - exts = self.Extensions() exts.update({key: value}) self.Set("extensions", exts) diff --git a/cloudevents/sdk/event/opt.py b/cloudevents/sdk/event/opt.py index 2a18a52a..bf630c32 100644 --- a/cloudevents/sdk/event/opt.py +++ b/cloudevents/sdk/event/opt.py @@ -35,3 +35,9 @@ def get(self): def required(self): return self.is_required + + def __eq__(self, obj): + return isinstance(obj, Option) and \ + obj.name == self.name and \ + obj.value == self.value and \ + obj.is_required == self.is_required diff --git a/cloudevents/sdk/event/v03.py b/cloudevents/sdk/event/v03.py index 4207e400..00ff67f8 100644 --- a/cloudevents/sdk/event/v03.py +++ b/cloudevents/sdk/event/v03.py @@ -68,6 +68,10 @@ def ContentType(self) -> str: 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 @@ -107,3 +111,7 @@ def SetContentType(self, contentType: str) -> base.BaseEvent: def SetContentEncoding(self, contentEncoding: str) -> base.BaseEvent: self.Set("datacontentencoding", contentEncoding) return self + + @datacontentencoding.setter + def datacontentencoding(self, value: str): + self.SetContentEncoding(value) diff --git a/cloudevents/sdk/http_events.py b/cloudevents/sdk/http_events.py index 4c5de1c2..8d8e7fa1 100644 --- a/cloudevents/sdk/http_events.py +++ b/cloudevents/sdk/http_events.py @@ -22,7 +22,7 @@ from cloudevents.sdk.event import v03, v1 -class CloudEvent(base.BaseEvent): +class CloudEvent(): """ Python-friendly cloudevent class supporting v1 events Currently only supports binary content mode CloudEvents diff --git a/cloudevents/tests/test_data_encaps_refs.py b/cloudevents/tests/test_data_encaps_refs.py new file mode 100644 index 00000000..84bc91ef --- /dev/null +++ b/cloudevents/tests/test_data_encaps_refs.py @@ -0,0 +1,114 @@ +# 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 io +import json +import copy +import pytest + +from uuid import uuid4 + +from cloudevents.sdk import converters +from cloudevents.sdk import marshaller + +from cloudevents.sdk.converters import structured +from cloudevents.sdk.event import v03, v1 + + +from cloudevents.tests import data + + +@pytest.mark.parametrize("event_class", [ v03.Event, v1.Event]) +def test_general_binary_properties(event_class): + m = marshaller.NewDefaultHTTPMarshaller() + event = m.FromRequest( + event_class(), + {"Content-Type": "application/cloudevents+json"}, + io.StringIO(json.dumps(data.json_ce[event_class])), + lambda x: x.read(), + ) + + new_headers, _ = m.ToRequest(event, converters.TypeBinary, lambda x: x) + assert new_headers is not None + assert "ce-specversion" in new_headers + + # Test properties + assert event is not None + assert event.type == data.ce_type + assert event.id == data.ce_id + assert event.content_type == data.contentType + assert event.source == data.source + + # Test setters + new_type = str(uuid4()) + new_id = str(uuid4()) + new_content_type = str(uuid4()) + new_source = str(uuid4()) + + event.extensions = {'test': str(uuid4)} + event.type = new_type + event.id = new_id + event.content_type = new_content_type + event.source = new_source + + assert event is not None + assert (event.type == new_type) and (event.type == event.EventType()) + assert (event.id == new_id) and (event.id == event.EventID()) + assert (event.content_type == new_content_type) and (event.content_type == event.ContentType()) + assert (event.source == new_source) and (event.source == event.Source()) + assert event.extensions['test'] == event.Extensions()['test'] + assert (event.specversion == event.CloudEventVersion()) + + +@pytest.mark.parametrize("event_class", [v03.Event, v1.Event]) +def test_general_structured_properties(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() + ) + # Test python properties + assert event is not None + assert event.type == data.ce_type + assert event.id == data.ce_id + assert event.content_type == data.contentType + assert event.source == data.source + + 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 + + # Test setters + new_type = str(uuid4()) + new_id = str(uuid4()) + new_content_type = str(uuid4()) + new_source = str(uuid4()) + + event.extensions = {'test': str(uuid4)} + event.type = new_type + event.id = new_id + event.content_type = new_content_type + event.source = new_source + + assert event is not None + assert (event.type == new_type) and (event.type == event.EventType()) + assert (event.id == new_id) and (event.id == event.EventID()) + assert (event.content_type == new_content_type) and (event.content_type == event.ContentType()) + assert (event.source == new_source) and (event.source == event.Source()) + assert event.extensions['test'] == event.Extensions()['test'] + assert (event.specversion == event.CloudEventVersion()) diff --git a/cloudevents/tests/test_http_events.py b/cloudevents/tests/test_http_events.py index 943e219e..843eb75f 100644 --- a/cloudevents/tests/test_http_events.py +++ b/cloudevents/tests/test_http_events.py @@ -101,6 +101,7 @@ def test_emit_binary_event(specversion): @pytest.mark.parametrize("specversion", ['1.0', '0.3']) def test_missing_ce_prefix_binary_event(specversion): + prefixed_headers = {} headers = { "ce-id": "my-id", "ce-source": "", @@ -108,16 +109,16 @@ def test_missing_ce_prefix_binary_event(specversion): "ce-specversion": specversion } for key in headers: - val = headers.pop(key) - + # breaking prefix e.g. e-id instead of ce-id - headers[key[1:]] = val + prefixed_headers[key[1:]] = headers[key] + with pytest.raises((TypeError, NotImplementedError)): # 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 - _ = CloudEvent(headers, test_data) + _ = CloudEvent(prefixed_headers, test_data) @pytest.mark.parametrize("specversion", ['1.0', '0.3'])