Skip to content

Implemented python properties in base.py #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 24, 2020
117 changes: 98 additions & 19 deletions cloudevents/sdk/event/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,61 +35,141 @@
# TODO(slinkydeveloper) is this really needed?
class EventGetterSetter(object):

# ce-specversion
def CloudEventVersion(self) -> str:
raise Exception("not implemented")
Comment on lines +38 to 40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be called CloudEventSpecversion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish, but unfortunately users are already using CloudEventVersion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make breaking changes for major versions of this package?


# 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)
Comment on lines +42 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe having 3 methods like this is too much, don't we just need 2, getter and setter/@property/@foo.setter?

Copy link
Contributor Author

@cumason123 cumason123 Jun 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why there's 4 methods (property/foo.setter and getter/setter) is the original developers made getters/setters. To make sure I didn't break any existing code, I had to make sure the old getters/setters still work which is why I didn't remove them. The benefit of the properties is now you can do something such as event.specversion which is more pythonic instead of event.CloudEventVersion()


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")
Comment on lines 91 to 92
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to SetID?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't rename the original setters/getters because that would break users code whom are using these functions


@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")
Comment on lines 106 to 107
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need these methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately yes. Removing them would break existing user code


@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:
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions cloudevents/sdk/event/opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions cloudevents/sdk/event/v03.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion cloudevents/sdk/http_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions cloudevents/tests/test_data_encaps_refs.py
Original file line number Diff line number Diff line change
@@ -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())
9 changes: 5 additions & 4 deletions cloudevents/tests/test_http_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,24 @@ 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": "<event-source>",
"ce-type": "cloudevent.event.type",
"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'])
Expand Down