Implement read method

Signed-off-by: Tudor Plugaru <plugaru.tudor@protonmail.com>
This commit is contained in:
Tudor Plugaru 2024-11-25 16:44:52 +02:00
parent fa0ec99bf2
commit 8aeed15f71
No known key found for this signature in database
7 changed files with 103 additions and 12 deletions

View File

@ -32,6 +32,7 @@ keywords = [
]
dependencies = [
"ruff>=0.6.8",
"python-dateutil>=2.8.2",
]
[project.urls]
@ -53,6 +54,7 @@ dev-dependencies = [
"flake8-print>=5.0.0",
"pre-commit>=3.8.0",
"pytest-cov>=5.0.0",
"types-python-dateutil>=2.9.0.20241003",
]
[tool.uv.pip]

View File

@ -18,6 +18,10 @@ from typing import Any, Optional, Protocol, Union
class BaseCloudEvent(Protocol):
def __init__(
self, attributes: dict[str, Any], data: Optional[Union[dict, str, bytes]] = None
) -> None: ...
def get_id(self) -> str: ...
def get_source(self) -> str: ...

View File

@ -21,4 +21,4 @@ from cloudevents.core.base import BaseCloudEvent
class Format(Protocol):
def read(self, data: Union[str, bytes]) -> BaseCloudEvent: ...
def write(self, event: BaseCloudEvent) -> str: ...
def write(self, event: BaseCloudEvent) -> bytes: ...

View File

@ -16,12 +16,16 @@
import base64
import re
from datetime import datetime
from json import JSONEncoder, dumps
from typing import Any, Final, Pattern, Union
from json import JSONEncoder, dumps, loads
from typing import Any, Final, Pattern, Type, TypeVar, Union
from dateutil.parser import isoparse
from cloudevents.core.base import BaseCloudEvent
from cloudevents.core.formats.base import Format
T = TypeVar("T", bound=BaseCloudEvent)
class _JSONEncoderWithDatetime(JSONEncoder):
"""
@ -46,10 +50,33 @@ class JSONFormat(Format):
r"^(application|text)\\/([a-zA-Z]+\\+)?json(;.*)*$"
)
def read(self, data: Union[str, bytes]) -> BaseCloudEvent:
pass
def read(self, event_klass: Type[T], data: Union[str, bytes]) -> T:
"""
Read a CloudEvent from a JSON formatted byte string.
def write(self, event: BaseCloudEvent) -> bytes:
:param data: The JSON formatted byte array.
:return: The CloudEvent instance.
"""
if isinstance(data, bytes):
decoded_data: str = data.decode("utf-8")
else:
decoded_data = data
event_attributes = loads(decoded_data)
if "time" in event_attributes:
event_attributes["time"] = isoparse(event_attributes["time"])
event_data: Union[str, bytes] = event_attributes.get("data")
if event_data is None:
event_data_base64 = event_attributes.get("data_base64")
if event_data_base64 is not None:
event_data = base64.b64decode(event_data_base64)
# disable mypy due to https://github.com/python/mypy/issues/9003
return event_klass(event_attributes, event_data) # type: ignore
def write(self, event: T) -> bytes:
"""
Write a CloudEvent to a JSON formatted byte string.
@ -57,7 +84,7 @@ class JSONFormat(Format):
:return: The CloudEvent as a JSON formatted byte array.
"""
event_data = event.get_data()
event_dict: dict[str, Any] = {**event.get_attributes()}
event_dict: dict[str, Any] = dict(event.get_attributes())
if event_data is not None:
if isinstance(event_data, (bytes, bytearray)):

View File

@ -15,7 +15,7 @@
import re
from collections import defaultdict
from datetime import datetime
from typing import Any, Final, Optional
from typing import Any, Final, Optional, Union
from cloudevents.core.base import BaseCloudEvent
from cloudevents.core.v1.exceptions import (
@ -45,7 +45,9 @@ class CloudEvent(BaseCloudEvent):
obliged to follow this contract.
"""
def __init__(self, attributes: dict[str, Any], data: Optional[dict] = None) -> None:
def __init__(
self, attributes: dict[str, Any], data: Optional[Union[dict, str, bytes]] = None
) -> None:
"""
Create a new CloudEvent instance.
@ -57,7 +59,7 @@ class CloudEvent(BaseCloudEvent):
"""
self._validate_attribute(attributes=attributes)
self._attributes: dict[str, Any] = attributes
self._data: Optional[dict] = data
self._data: Optional[Union[dict, str, bytes]] = data
@staticmethod
def _validate_attribute(attributes: dict[str, Any]) -> None:
@ -316,7 +318,7 @@ class CloudEvent(BaseCloudEvent):
"""
return self._attributes.get(extension_name)
def get_data(self) -> Optional[dict]:
def get_data(self) -> Optional[Union[dict, str, bytes]]:
"""
Retrieve data of the event.

View File

@ -155,3 +155,23 @@ def test_write_cloud_event_to_json_with_no_content_type_set_and_data_as_json() -
"utf-8"
)
)
def test_read_cloud_event_from_json_with_attributes_only() -> None:
data = '{"id": "123", "source": "source", "type": "type", "specversion": "1.0", "time": "2023-10-25T17:09:19.736166Z", "datacontenttype": "application/json", "dataschema": "http://example.com/schema", "subject": "test_subject"}'.encode(
"utf-8"
)
formatter = JSONFormat()
result = formatter.read(CloudEvent, data)
assert result.get_id() == "123"
assert result.get_source() == "source"
assert result.get_type() == "type"
assert result.get_specversion() == "1.0"
assert result.get_time() == datetime(
2023, 10, 25, 17, 9, 19, 736166, tzinfo=timezone.utc
)
assert result.get_datacontenttype() == "application/json"
assert result.get_dataschema() == "http://example.com/schema"
assert result.get_subject() == "test_subject"
assert result.get_data() is None

38
uv.lock
View File

@ -15,6 +15,7 @@ name = "cloudevents"
version = "2.0.0a1"
source = { editable = "." }
dependencies = [
{ name = "python-dateutil" },
{ name = "ruff" },
]
@ -28,10 +29,14 @@ dev = [
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "types-python-dateutil" },
]
[package.metadata]
requires-dist = [{ name = "ruff", specifier = ">=0.6.8" }]
requires-dist = [
{ name = "python-dateutil", specifier = ">=2.8.2" },
{ name = "ruff", specifier = ">=0.6.8" },
]
[package.metadata.requires-dev]
dev = [
@ -43,6 +48,7 @@ dev = [
{ name = "pre-commit", specifier = ">=3.8.0" },
{ name = "pytest", specifier = ">=8.3.3" },
{ name = "pytest-cov", specifier = ">=5.0.0" },
{ name = "types-python-dateutil", specifier = ">=2.9.0.20241003" },
]
[[package]]
@ -373,6 +379,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990 },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
@ -451,6 +469,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/bd/a8b0c64945a92eaeeb8d0283f27a726a776a1c9d12734d990c5fc7a1278c/ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc", size = 8669595 },
]
[[package]]
name = "six"
version = "1.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
]
[[package]]
name = "tomli"
version = "2.0.1"
@ -460,6 +487,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 },
]
[[package]]
name = "types-python-dateutil"
version = "2.9.0.20241003"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/31/f8/f6ee4c803a7beccffee21bb29a71573b39f7037c224843eff53e5308c16e/types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446", size = 9210 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/35/d6/ba5f61958f358028f2e2ba1b8e225b8e263053bd57d3a79e2d2db64c807b/types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d", size = 9693 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"