From 8874fe4ca9345ab902b86f6bb821b827c887add0 Mon Sep 17 00:00:00 2001 From: StatPan Date: Tue, 21 Apr 2026 21:35:33 +0900 Subject: [PATCH 1/2] compat: finalize json_encoder parity for #3 --- CHANGELOG.md | 17 +++++++---------- COMPATIBILITY_CHECKLIST.md | 5 +++-- Cargo.toml | 2 +- VERSIONING.md | 2 +- pyproject.toml | 2 +- python/jwt_rs/__init__.py | 2 +- tests/test_upstream_api_jws.py | 35 ++++++++++++++++++++++++++++++++++ tests/test_upstream_api_jwt.py | 19 ++++++++++++++++++ 8 files changed, 68 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a372e..7b1e48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,14 @@ is documented in `VERSIONING.md`. ## [Unreleased] -### Changed +### Fixed -- Release workflow now publishes to PyPI via **Trusted Publishing (OIDC)** - instead of the `PYPI_API_TOKEN` fallback that shipped `v1.2.0`. Added - `id-token: write` / `attestations: write` permissions to the publish - job and removed the explicit `password:` argument. The `environment: - pypi` gate is preserved. PEP 740 attestations are produced for every - artifact, which were disabled under the token path. -- Bumped `actions/download-artifact` from `v5` (Node 20) to `v8` - (Node 24) to clear the runner deprecation annotation. All other - actions already run on Node 24. +- Added regression coverage for `json_encoder` parity so custom payload + values still encode correctly after datetime claim normalization, custom + header values use the same encoder path as upstream PyJWT, and `kid` + validation is not bypassed by a custom encoder. +- Marked `json_encoder` compatibility as complete in + `COMPATIBILITY_CHECKLIST.md`. ## [1.2.0] - 2026-04-21 diff --git a/COMPATIBILITY_CHECKLIST.md b/COMPATIBILITY_CHECKLIST.md index e2e6ef1..dba5c68 100644 --- a/COMPATIBILITY_CHECKLIST.md +++ b/COMPATIBILITY_CHECKLIST.md @@ -59,8 +59,9 @@ 근거: [python/jwt_rs/api_jws.py](/home/statpan/workspace/pypi_lib/pyjwt-rs/python/jwt_rs/api_jws.py:153) - [x] detached payload (`b64=False`) 지원 근거: [python/jwt_rs/api_jws.py](/home/statpan/workspace/pypi_lib/pyjwt-rs/python/jwt_rs/api_jws.py:148) -- [-] `json_encoder` 완전 호환 - `json.dumps(..., cls=json_encoder)` 는 지원하지만 upstream의 전체 커스텀 알고리즘/객체 조합까지 검증하진 않음 +- [x] `json_encoder` 호환 + payload / header 모두 `json.dumps(..., cls=json_encoder)` 경로를 사용하며, + datetime claim 변환 이후의 payload 값과 비표준 header 값에 대한 회귀 테스트를 추가해 동작을 고정함 ## 4. Decode / Validation Behavior diff --git a/Cargo.toml b/Cargo.toml index 4a327d8..54b4680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyjwt-rs" -version = "1.2.0" +version = "1.2.1" edition = "2024" description = "Rust-backed PyJWT-compatible JWT library for Python" license = "MIT" diff --git a/VERSIONING.md b/VERSIONING.md index 01bf679..5932386 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -12,7 +12,7 @@ 현재 기준: -- `pyjwt-rs`: `1.2.0` +- `pyjwt-rs`: `1.2.1` ## 2. Compatibility Version diff --git a/pyproject.toml b/pyproject.toml index eedea93..d3bea12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "pyjwt-rs" -version = "1.2.0" +version = "1.2.1" description = "Rust-backed PyJWT-compatible JWT library for Python" readme = "README.md" requires-python = ">=3.10" diff --git a/python/jwt_rs/__init__.py b/python/jwt_rs/__init__.py index 8ae61cd..1e18db8 100644 --- a/python/jwt_rs/__init__.py +++ b/python/jwt_rs/__init__.py @@ -34,7 +34,7 @@ __version__ = "2.12.1" # Distribution version of the pyjwt-rs package itself (separate from the PyJWT # compatibility version above). See VERSIONING.md for the two-axis policy. -__pyjwt_rs_version__ = "1.2.0" +__pyjwt_rs_version__ = "1.2.1" __title__ = "PyJWT" __description__ = "JSON Web Token implementation in Python" diff --git a/tests/test_upstream_api_jws.py b/tests/test_upstream_api_jws.py index b4b76db..9abd456 100644 --- a/tests/test_upstream_api_jws.py +++ b/tests/test_upstream_api_jws.py @@ -842,6 +842,41 @@ def default(self, o: object) -> str: assert "some_decimal" in header assert header["some_decimal"] == "it worked" + def test_custom_json_encoder_handles_nonstandard_header_values( + self, jws: PyJWS, payload: bytes + ) -> None: + class CustomJSONEncoder(json.JSONEncoder): + def default(self, o: object) -> list[int]: + assert isinstance(o, set) + return sorted(o) + + token = jws.encode( + payload, + HS256_SECRET, + headers={"x-custom": {3, 1, 2}}, + json_encoder=CustomJSONEncoder, + ) + + header, *_ = token.split(".") + header = json.loads(base64url_decode(header)) + + assert header["x-custom"] == [1, 2, 3] + + def test_custom_json_encoder_does_not_bypass_kid_validation( + self, jws: PyJWS, payload: bytes + ) -> None: + class CustomJSONEncoder(json.JSONEncoder): + def default(self, o: object) -> str: + return "serialized" + + with pytest.raises(InvalidTokenError, match="Key ID header parameter must be a string"): + jws.encode( + payload, + HS256_SECRET, + headers={"kid": object()}, + json_encoder=CustomJSONEncoder, + ) + def test_encode_headers_parameter_adds_headers( self, jws: PyJWS, payload: bytes ) -> None: diff --git a/tests/test_upstream_api_jwt.py b/tests/test_upstream_api_jwt.py index 555ca63..28f7594 100644 --- a/tests/test_upstream_api_jwt.py +++ b/tests/test_upstream_api_jwt.py @@ -733,6 +733,25 @@ def default(self, o: object) -> str: assert payload == {"some_decimal": "it worked"} + def test_custom_json_encoder_handles_nested_values_after_datetime_conversion( + self, jwt: PyJWT + ) -> None: + class CustomJSONEncoder(json.JSONEncoder): + def default(self, o: object) -> list[int]: + assert isinstance(o, set) + return sorted(o) + + data = { + "exp": datetime.now(tz=timezone.utc) + timedelta(minutes=5), + "values": {3, 1, 2}, + } + + token = jwt.encode(data, HS256_SECRET, json_encoder=CustomJSONEncoder) + payload = jwt.decode(token, HS256_SECRET, algorithms=["HS256"]) + + assert isinstance(payload["exp"], int) + assert payload["values"] == [1, 2, 3] + def test_decode_with_verify_exp_option( self, jwt: PyJWT, payload: dict[str, object] ) -> None: From 0cf40bcab7e9ca4015ca18ad2b6cc012b31bb379 Mon Sep 17 00:00:00 2001 From: StatPan Date: Tue, 21 Apr 2026 21:38:08 +0900 Subject: [PATCH 2/2] build: drop manual release metadata changes from #3 --- CHANGELOG.md | 9 --------- Cargo.toml | 2 +- VERSIONING.md | 2 +- pyproject.toml | 2 +- python/jwt_rs/__init__.py | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1e48b..8103a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,6 @@ is documented in `VERSIONING.md`. ## [Unreleased] -### Fixed - -- Added regression coverage for `json_encoder` parity so custom payload - values still encode correctly after datetime claim normalization, custom - header values use the same encoder path as upstream PyJWT, and `kid` - validation is not bypassed by a custom encoder. -- Marked `json_encoder` compatibility as complete in - `COMPATIBILITY_CHECKLIST.md`. - ## [1.2.0] - 2026-04-21 ## [1.1.3] - 2026-04-21 diff --git a/Cargo.toml b/Cargo.toml index 54b4680..4a327d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyjwt-rs" -version = "1.2.1" +version = "1.2.0" edition = "2024" description = "Rust-backed PyJWT-compatible JWT library for Python" license = "MIT" diff --git a/VERSIONING.md b/VERSIONING.md index 5932386..01bf679 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -12,7 +12,7 @@ 현재 기준: -- `pyjwt-rs`: `1.2.1` +- `pyjwt-rs`: `1.2.0` ## 2. Compatibility Version diff --git a/pyproject.toml b/pyproject.toml index d3bea12..eedea93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "pyjwt-rs" -version = "1.2.1" +version = "1.2.0" description = "Rust-backed PyJWT-compatible JWT library for Python" readme = "README.md" requires-python = ">=3.10" diff --git a/python/jwt_rs/__init__.py b/python/jwt_rs/__init__.py index 1e18db8..8ae61cd 100644 --- a/python/jwt_rs/__init__.py +++ b/python/jwt_rs/__init__.py @@ -34,7 +34,7 @@ __version__ = "2.12.1" # Distribution version of the pyjwt-rs package itself (separate from the PyJWT # compatibility version above). See VERSIONING.md for the two-axis policy. -__pyjwt_rs_version__ = "1.2.1" +__pyjwt_rs_version__ = "1.2.0" __title__ = "PyJWT" __description__ = "JSON Web Token implementation in Python"