refactor: expose data and attributes in class

Signed-off-by: Alexander Tkachev <sasha64sasha@gmail.com>
This commit is contained in:
Alexander Tkachev 2022-07-25 01:02:17 +03:00
parent c0b54130c6
commit 065ef91277
3 changed files with 25 additions and 47 deletions

View File

@ -26,55 +26,27 @@ class CloudEvent:
raise NotImplementedError() raise NotImplementedError()
@property @property
def _attributes_read_model(self) -> typing.Dict[str, typing.Any]: def attributes(self) -> typing.Dict[str, typing.Any]:
""" """
:return: Attributes of this event. :return: Attributes of this event.
You MUST NOT mutate this dict. You MUST NOT mutate this dict.
Implementation MAY assume the dict will not be mutated. Implementation MAY assume the dict will not be mutated.
The reason this is only a read model and not a write model is
because you MAY not have an easy write access to the data.
For example - a database row or a cached value.
We don't wont to restrict our future selves.
When a write model will be needed, it will be implemented
The we don't have an `attributes` property is to prevent API confusion
Examples of confusion:
* What is the difference between `event.get("myattr")` and
`event.attributes.get("myattr")`
* What SHOULD I use `event["myattr"]` or `event.attributes["myattr"]` ?
""" """
raise NotImplementedError() raise NotImplementedError()
@property @property
def _data_read_model(self) -> typing.Optional[typing.Any]: def data(self) -> typing.Optional[typing.Any]:
""" """
:return: Data value of the event. :return: Data value of the event.
You MUST NOT mutate this dict. You MUST NOT mutate this dict.
Implementation MAY assume the dict will not be mutated. Implementation MAY assume the dict will not be mutated.
The reason this is only a read model and not a write model is
because you MAY not have an easy write access to the data.
For example - a database row or a cached value.
We don't wont to restrict our future selves.
When a write model will be needed, it will be implemented
""" """
raise NotImplementedError() raise NotImplementedError()
def __eq__(self, other: typing.Any) -> bool: def __eq__(self, other: typing.Any) -> bool:
if isinstance(other, CloudEvent): if isinstance(other, CloudEvent):
return ( return self.data == other.data and self.attributes == other.attributes
self._data_read_model == other._data_read_model
and self._attributes_read_model == other._attributes_read_model
)
return False return False
def __getitem__(self, key: str) -> typing.Any: def __getitem__(self, key: str) -> typing.Any:
@ -84,7 +56,7 @@ class CloudEvent:
:param key: The event attribute name. :param key: The event attribute name.
:return: The event attribute value. :return: The event attribute value.
""" """
return self._attributes_read_model[key] return self.attributes[key]
def get( def get(
self, key: str, default: typing.Optional[typing.Any] = None self, key: str, default: typing.Optional[typing.Any] = None
@ -100,21 +72,19 @@ class CloudEvent:
no attribute with the given key exists. no attribute with the given key exists.
:returns: The event attribute value if exists, default value otherwise. :returns: The event attribute value if exists, default value otherwise.
""" """
return self._attributes_read_model.get(key, default) return self.attributes.get(key, default)
def __iter__(self) -> typing.Iterator[typing.Any]: def __iter__(self) -> typing.Iterator[typing.Any]:
return iter(self._attributes_read_model) return iter(self.attributes)
def __len__(self) -> int: def __len__(self) -> int:
return len(self._attributes_read_model) return len(self.attributes)
def __contains__(self, key: str) -> bool: def __contains__(self, key: str) -> bool:
return key in self._attributes_read_model return key in self.attributes
def __repr__(self) -> str: def __repr__(self) -> str:
return str( return str({"attributes": self.attributes, "data": self.data})
{"attributes": self._attributes_read_model, "data": self._data_read_model}
)
AnyCloudEvent = TypeVar("AnyCloudEvent", bound=CloudEvent) AnyCloudEvent = TypeVar("AnyCloudEvent", bound=CloudEvent)

View File

@ -52,7 +52,7 @@ class CloudEvent(abstract.CloudEvent):
:type data: typing.Any :type data: typing.Any
""" """
self._attributes = {k.lower(): v for k, v in attributes.items()} self._attributes = {k.lower(): v for k, v in attributes.items()}
self.data = data self._data = data
if "specversion" not in self._attributes: if "specversion" not in self._attributes:
self._attributes["specversion"] = "1.0" self._attributes["specversion"] = "1.0"
if "id" not in self._attributes: if "id" not in self._attributes:
@ -75,12 +75,20 @@ class CloudEvent(abstract.CloudEvent):
) )
@property @property
def _attributes_read_model(self) -> typing.Dict[str, typing.Any]: def attributes(self) -> typing.Dict[str, typing.Any]:
return self._attributes return self._attributes
@property @property
def _data_read_model(self) -> typing.Optional[typing.Any]: def data(self) -> typing.Optional[typing.Any]:
return self.data return self._data
@data.setter
def data(self, value) -> None:
"""
Exists for backwards compatibility
:param value: new data vale
"""
self._data = value
def __setitem__(self, key: str, value: typing.Any) -> None: def __setitem__(self, key: str, value: typing.Any) -> None:
self._attributes[key] = value self._attributes[key] = value

View File

@ -27,17 +27,17 @@ def test_create_is_abstract():
assert CloudEvent.create({}, None) is None assert CloudEvent.create({}, None) is None
def test_data_read_is_abstract(): def test_data_is_abstract():
""" """
exists mainly for coverage reasons exists mainly for coverage reasons
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
CloudEvent()._data_read_model CloudEvent().data
def test_attributes_read_model_is_abstract(): def test_attributes_is_abstract():
""" """
exists mainly for coverage reasons exists mainly for coverage reasons
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
CloudEvent()._attributes_read_model CloudEvent().attributes