Skip to content

Commit 8aeed15

Browse files
committed
Implement read method
Signed-off-by: Tudor Plugaru <plugaru.tudor@protonmail.com>
1 parent fa0ec99 commit 8aeed15

File tree

7 files changed

+103
-12
lines changed

7 files changed

+103
-12
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ keywords = [
3232
]
3333
dependencies = [
3434
"ruff>=0.6.8",
35+
"python-dateutil>=2.8.2",
3536
]
3637

3738
[project.urls]
@@ -53,6 +54,7 @@ dev-dependencies = [
5354
"flake8-print>=5.0.0",
5455
"pre-commit>=3.8.0",
5556
"pytest-cov>=5.0.0",
57+
"types-python-dateutil>=2.9.0.20241003",
5658
]
5759

5860
[tool.uv.pip]

src/cloudevents/core/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818

1919

2020
class BaseCloudEvent(Protocol):
21+
def __init__(
22+
self, attributes: dict[str, Any], data: Optional[Union[dict, str, bytes]] = None
23+
) -> None: ...
24+
2125
def get_id(self) -> str: ...
2226

2327
def get_source(self) -> str: ...

src/cloudevents/core/formats/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
class Format(Protocol):
2222
def read(self, data: Union[str, bytes]) -> BaseCloudEvent: ...
2323

24-
def write(self, event: BaseCloudEvent) -> str: ...
24+
def write(self, event: BaseCloudEvent) -> bytes: ...

src/cloudevents/core/formats/json.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
import base64
1717
import re
1818
from datetime import datetime
19-
from json import JSONEncoder, dumps
20-
from typing import Any, Final, Pattern, Union
19+
from json import JSONEncoder, dumps, loads
20+
from typing import Any, Final, Pattern, Type, TypeVar, Union
21+
22+
from dateutil.parser import isoparse
2123

2224
from cloudevents.core.base import BaseCloudEvent
2325
from cloudevents.core.formats.base import Format
2426

27+
T = TypeVar("T", bound=BaseCloudEvent)
28+
2529

2630
class _JSONEncoderWithDatetime(JSONEncoder):
2731
"""
@@ -46,18 +50,41 @@ class JSONFormat(Format):
4650
r"^(application|text)\\/([a-zA-Z]+\\+)?json(;.*)*$"
4751
)
4852

49-
def read(self, data: Union[str, bytes]) -> BaseCloudEvent:
50-
pass
53+
def read(self, event_klass: Type[T], data: Union[str, bytes]) -> T:
54+
"""
55+
Read a CloudEvent from a JSON formatted byte string.
56+
57+
:param data: The JSON formatted byte array.
58+
:return: The CloudEvent instance.
59+
"""
60+
if isinstance(data, bytes):
61+
decoded_data: str = data.decode("utf-8")
62+
else:
63+
decoded_data = data
64+
65+
event_attributes = loads(decoded_data)
66+
67+
if "time" in event_attributes:
68+
event_attributes["time"] = isoparse(event_attributes["time"])
69+
70+
event_data: Union[str, bytes] = event_attributes.get("data")
71+
if event_data is None:
72+
event_data_base64 = event_attributes.get("data_base64")
73+
if event_data_base64 is not None:
74+
event_data = base64.b64decode(event_data_base64)
75+
76+
# disable mypy due to https://github.com/python/mypy/issues/9003
77+
return event_klass(event_attributes, event_data) # type: ignore
5178

52-
def write(self, event: BaseCloudEvent) -> bytes:
79+
def write(self, event: T) -> bytes:
5380
"""
5481
Write a CloudEvent to a JSON formatted byte string.
5582
5683
:param event: The CloudEvent to write.
5784
:return: The CloudEvent as a JSON formatted byte array.
5885
"""
5986
event_data = event.get_data()
60-
event_dict: dict[str, Any] = {**event.get_attributes()}
87+
event_dict: dict[str, Any] = dict(event.get_attributes())
6188

6289
if event_data is not None:
6390
if isinstance(event_data, (bytes, bytearray)):

src/cloudevents/core/v1/event.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import re
1616
from collections import defaultdict
1717
from datetime import datetime
18-
from typing import Any, Final, Optional
18+
from typing import Any, Final, Optional, Union
1919

2020
from cloudevents.core.base import BaseCloudEvent
2121
from cloudevents.core.v1.exceptions import (
@@ -45,7 +45,9 @@ class CloudEvent(BaseCloudEvent):
4545
obliged to follow this contract.
4646
"""
4747

48-
def __init__(self, attributes: dict[str, Any], data: Optional[dict] = None) -> None:
48+
def __init__(
49+
self, attributes: dict[str, Any], data: Optional[Union[dict, str, bytes]] = None
50+
) -> None:
4951
"""
5052
Create a new CloudEvent instance.
5153
@@ -57,7 +59,7 @@ def __init__(self, attributes: dict[str, Any], data: Optional[dict] = None) -> N
5759
"""
5860
self._validate_attribute(attributes=attributes)
5961
self._attributes: dict[str, Any] = attributes
60-
self._data: Optional[dict] = data
62+
self._data: Optional[Union[dict, str, bytes]] = data
6163

6264
@staticmethod
6365
def _validate_attribute(attributes: dict[str, Any]) -> None:
@@ -316,7 +318,7 @@ def get_extension(self, extension_name: str) -> Any:
316318
"""
317319
return self._attributes.get(extension_name)
318320

319-
def get_data(self) -> Optional[dict]:
321+
def get_data(self) -> Optional[Union[dict, str, bytes]]:
320322
"""
321323
Retrieve data of the event.
322324

tests/test_core/test_format/test_json.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,23 @@ def test_write_cloud_event_to_json_with_no_content_type_set_and_data_as_json() -
155155
"utf-8"
156156
)
157157
)
158+
159+
160+
def test_read_cloud_event_from_json_with_attributes_only() -> None:
161+
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(
162+
"utf-8"
163+
)
164+
formatter = JSONFormat()
165+
result = formatter.read(CloudEvent, data)
166+
167+
assert result.get_id() == "123"
168+
assert result.get_source() == "source"
169+
assert result.get_type() == "type"
170+
assert result.get_specversion() == "1.0"
171+
assert result.get_time() == datetime(
172+
2023, 10, 25, 17, 9, 19, 736166, tzinfo=timezone.utc
173+
)
174+
assert result.get_datacontenttype() == "application/json"
175+
assert result.get_dataschema() == "http://example.com/schema"
176+
assert result.get_subject() == "test_subject"
177+
assert result.get_data() is None

uv.lock

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)