refactor: expose data and attributes in class
Signed-off-by: Alexander Tkachev <sasha64sasha@gmail.com>
This commit is contained in:
parent
c0b54130c6
commit
065ef91277
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue