Skip to content

Commit ee06035

Browse files
authored
Merge branch 'main' into update-maintainers
2 parents ce93b77 + 4a689cb commit ee06035

9 files changed

Lines changed: 393 additions & 275 deletions

File tree

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
steps:
99
- uses: actions/checkout@v6
1010
- name: Install uv
11-
uses: astral-sh/setup-uv@v7
11+
uses: astral-sh/setup-uv@v8.1.0
1212
with:
1313
enable-cache: true
1414
cache-dependency-glob: "uv.lock"
@@ -28,7 +28,7 @@ jobs:
2828
steps:
2929
- uses: actions/checkout@v6
3030
- name: Install uv
31-
uses: astral-sh/setup-uv@v7
31+
uses: astral-sh/setup-uv@v8.1.0
3232
with:
3333
enable-cache: true
3434
cache-dependency-glob: "uv.lock"

.github/workflows/pypi-release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
with:
1717
fetch-depth: 0
1818
- name: Install uv
19-
uses: astral-sh/setup-uv@v7
19+
uses: astral-sh/setup-uv@v8.1.0
2020
- name: Set up Python
2121
run: uv python install 3.12
2222
- name: Install the project
@@ -25,7 +25,7 @@ jobs:
2525
- name: Build SDist and wheel
2626
run: uv build
2727

28-
- uses: actions/upload-artifact@v7
28+
- uses: actions/upload-artifact@v7.0.1
2929
with:
3030
name: artifact
3131
path: dist/*
@@ -40,7 +40,7 @@ jobs:
4040
- uses: actions/checkout@v6
4141
with:
4242
fetch-depth: 0
43-
- uses: actions/download-artifact@v7
43+
- uses: actions/download-artifact@v8.0.1
4444
with:
4545
name: artifact
4646
path: dist

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ repos:
66
- id: end-of-file-fixer
77
- id: check-toml
88
- repo: https://github.com/astral-sh/ruff-pre-commit
9-
rev: v0.15.7
9+
rev: v0.15.14
1010
hooks:
1111
# Run the linter.
1212
- id: ruff
1313
# Run the formatter.
1414
- id: ruff-format
1515
- repo: https://github.com/pre-commit/mirrors-mypy
16-
rev: v1.19.1
16+
rev: v1.20.2
1717
hooks:
1818
- id: mypy
1919
files: ^(src/cloudevents/|tests/)
2020
exclude: ^(src/cloudevents/v1/|tests/test_v1_compat/)
2121
types: [python]
2222
args: ["--config-file=pyproject.toml"]
2323
additional_dependencies:
24-
- types-python-dateutil>=2.9.0.20260305
24+
- types-python-dateutil>=2.9.0.20260518

CHANGELOG.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [2.2.0]
10+
11+
### Changed
12+
13+
- Updated dev and build-time dependencies.
14+
15+
### Fixed
16+
17+
- Fixed an issue where setting a non-JSON datacontenttype (e.g.,
18+
application/octet-stream) with dict data produced non-JSON-decodable output; now
19+
best-effort json-encoding is applied regardless of datacontenttype. ([#291])
20+
- Updated behavior when datacontenttype is unset: now treats events as application/json
21+
in line with spec recommendation. ([#291])
22+
23+
## [2.1.0]
24+
925
### Added
1026

11-
- Added [RabbitMQ](https://github.com/knative-extensions/eventing-rabbitmq/blob/main/cloudevents-protocol-spec/spec.md)
12-
protocol binding for CloudEvents.
27+
- Added [RabbitMQ][rabbit-mq-spec] protocol binding for CloudEvents. ([#284])
28+
29+
[rabbit-mq-spec]: https://github.com/knative-extensions/eventing-rabbitmq/blob/main/cloudevents-protocol-spec/spec.md
1330

1431
## [2.0.0]
1532

@@ -349,3 +366,5 @@ CloudEvents v2 is a rewrite with ongoing development ([#271])
349366
[#276]: https://github.com/cloudevents/sdk-python/pull/276
350367
[#278]: https://github.com/cloudevents/sdk-python/pull/278
351368
[#279]: https://github.com/cloudevents/sdk-python/pull/279
369+
[#284]: https://github.com/cloudevents/sdk-python/pull/284
370+
[#291]: https://github.com/cloudevents/sdk-python/pull/291

pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ build-backend = "hatchling.build"
4747

4848
[dependency-groups]
4949
dev = [
50-
"ruff>=0.15.7",
51-
"pytest>=9.0.2",
52-
"mypy>=1.19.1",
50+
"ruff>=0.15.14",
51+
"pytest>=9.0.3",
52+
"mypy>=1.20.2",
5353
"isort>=8.0.1",
5454
"flake8>=7.3.0",
5555
"pep8-naming>=0.15.1",
5656
"flake8-print>=5.0.0",
57-
"pre-commit>=4.5.1",
57+
"pre-commit>=4.6.0",
5858
"pytest-cov>=7.1.0",
59-
"types-python-dateutil>=2.9.0.20260305",
59+
"types-python-dateutil>=2.9.0.20260518",
6060
"sanic>=25.12.0",
6161
"sanic-testing>=24.6.0",
62-
"pydantic>=2.12.5",
62+
"pydantic>=2.13.4",
6363
]
6464

6565
[tool.uv.pip]

src/cloudevents/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15-
__version__ = "2.0.0"
15+
__version__ = "2.2.0"

src/cloudevents/core/formats/json.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def default(self, obj: Any) -> Any:
4444

4545
class JSONFormat(Format):
4646
CONTENT_TYPE: Final[str] = "application/cloudevents+json"
47+
DEFAULT_CONTENT_TYPE: Final[str] = "application/json"
4748
JSON_CONTENT_TYPE_PATTERN: Pattern[str] = re.compile(
4849
r"^(application|text)/([a-zA-Z0-9\-\.]+\+)?json(;.*)?$"
4950
)
@@ -135,7 +136,9 @@ def write(self, event: BaseCloudEvent) -> bytes:
135136
"utf-8"
136137
)
137138
else:
138-
datacontenttype = event_dict.get("datacontenttype", "application/json")
139+
datacontenttype = event_dict.get(
140+
"datacontenttype", self.DEFAULT_CONTENT_TYPE
141+
)
139142
if re.match(JSONFormat.JSON_CONTENT_TYPE_PATTERN, datacontenttype):
140143
event_dict["data"] = event_data
141144
else:
@@ -171,12 +174,19 @@ def write_data(
171174

172175
# If data is a dict and content type is JSON, serialize as JSON
173176
if isinstance(data, dict):
174-
if datacontenttype and re.match(
175-
JSONFormat.JSON_CONTENT_TYPE_PATTERN, datacontenttype
176-
):
177+
content_type = datacontenttype or self.DEFAULT_CONTENT_TYPE
178+
if re.match(JSONFormat.JSON_CONTENT_TYPE_PATTERN, content_type):
177179
return dumps(data, cls=_JSONEncoderWithDatetime).encode("utf-8")
178180

179-
# Default: convert to string and encode
181+
# for other contenttypes we still try to generate a json-decodable string
182+
# if not possible, a string representing
183+
try:
184+
return dumps(data, cls=_JSONEncoderWithDatetime).encode("utf-8")
185+
except TypeError:
186+
pass
187+
188+
# according to the spec, we return an encoded string per default
189+
# careful: the result is not json-decodable as the dict keys are single-quoted
180190
return str(data).encode("utf-8")
181191

182192
def read_data(
@@ -196,9 +206,8 @@ def read_data(
196206
return None
197207

198208
# If content type indicates JSON, try to parse as JSON
199-
if datacontenttype and re.match(
200-
JSONFormat.JSON_CONTENT_TYPE_PATTERN, datacontenttype
201-
):
209+
content_type = datacontenttype or self.DEFAULT_CONTENT_TYPE
210+
if re.match(JSONFormat.JSON_CONTENT_TYPE_PATTERN, content_type):
202211
try:
203212
decoded = body.decode("utf-8")
204213
parsed: dict[str, Any] = loads(decoded)

tests/test_core/test_format/test_json.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414

15-
1615
from datetime import datetime, timezone
16+
from json import loads
17+
18+
import pytest
1719

1820
from cloudevents.core.formats.json import JSONFormat
1921
from cloudevents.core.v1.event import CloudEvent
@@ -323,3 +325,24 @@ def test_read_cloud_event_from_string_input() -> None:
323325

324326
assert result.get_id() == "123"
325327
assert result.get_source() == "source"
328+
329+
330+
@pytest.mark.parametrize(
331+
"content_type", [None, "application/json", "application/octet-stream"]
332+
)
333+
def test_write_data_dict(content_type: str) -> None:
334+
formatter = JSONFormat()
335+
data = {"key": "value", "nested": {"a": 1}}
336+
result = formatter.write_data(data, datacontenttype=content_type)
337+
338+
assert isinstance(result, bytes)
339+
assert loads(result) == data
340+
341+
342+
@pytest.mark.parametrize("content_type", [None, "application/json"])
343+
def test_read_data_json_body(content_type: str) -> None:
344+
formatter = JSONFormat()
345+
body = b'{"key": "value"}'
346+
result = formatter.read_data(body, content_type)
347+
348+
assert result == {"key": "value"}

0 commit comments

Comments
 (0)