Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ on:
branches:
- 'develop'
- 'release/**'
workflow_dispatch:
inputs:
publish:
description: 'Publish the validated artifacts to TestPyPI'
required: false
default: false
type: boolean

permissions:
contents: read
id-token: write

jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to TestPyPI
name: Build Python 🐍 distributions 📦 and optionally publish to TestPyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand All @@ -20,11 +28,12 @@ jobs:
with:
python-version: '3.12'

- name: Install pypa/build
- name: Install packaging tools
run: >-
python -m
pip install
build
twine
--user
- name: Build a binary wheel and a source tarball
run: >-
Expand All @@ -35,10 +44,14 @@ jobs:
--outdir dist/
.

- name: Check distribution metadata
run: python -m twine check dist/*

- name: Publish distribution 📦 to Test PyPI
if: github.event_name == 'workflow_dispatch' && inputs.publish
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
skip_existing: true
repository-url: https://test.pypi.org/legacy/
skip-existing: true
11 changes: 11 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@

6.5.0 / 2026-07-15
==================

* perf: reduce pure Python serializer time by 31.1% on the deterministic 5,000-record benchmark through exact native-type dispatch
* perf: release ``json2xml-rs`` 0.4.2 with hybrid SIMD/word XML escape scanning and a 6.23% benchmark reduction
* fix: preserve numeric, string, dictionary, and sequence subclass behavior across the optimized Python dispatch paths
* test: require exactly 100% statement coverage from the complete Python suite and add dense Rust XML escape scanning regression coverage
* chore: require ``json2xml-rs>=0.4.2`` from ``json2xml[fast]`` and validate release wheels before PyPI publication
* docs: add Python and Rust before/after flamegraphs, buffer-capacity measurements, and release performance notes


6.4.0 / 2026-07-13
==================

Expand Down
42 changes: 42 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# json2xml 6.5.0

Released 2026-07-15.

## Highlights

- Reduced pure Python serializer time by 31.1% on the deterministic 5,000-record workload by using exact native-type dispatch on hot paths while preserving subclass fallbacks.
- Added explicit regression coverage for `Decimal`, `Fraction`, complex, custom `Number`, string, dictionary, list, and tuple subclasses.
- Made the complete Python suite an exact 100% statement-coverage gate: 421 tests cover all 762 statements.
- Updated `json2xml[fast]` to require the published `json2xml-rs>=0.4.2` accelerator.

## Performance

All profiles used uv-managed CPython 3.15.0b3 and the same deterministic 5,000-record nested payload.

| Pure Python metric | Before | After | Change |
| --- | ---: | ---: | ---: |
| Conversion time | 83.0 ms | 57.2 ms | 31.1% lower |
| 20-loop traced time | 8.311 s | 5.782 s | 30.4% lower |
| Function calls | 48.17 million | 30.13 million | 37.4% fewer |
| `isinstance` calls | 11.70 million | 2.80 million | 76.1% fewer |

The Rust 0.4.2 accelerator released first and improved its paired release median from 6.007 ms to 5.632 ms, or 6.23%, with identical 4,093,244-byte output. Its hybrid scanner keeps dense XML escape input linear, and the measured 16 KiB streaming buffer remains unchanged.

## Package Versions

- Python package: `json2xml==6.5.0`
- Rust accelerator: `json2xml-rs==0.4.2`
- Fast install: `pip install "json2xml[fast]==6.5.0"`

## Profiling Evidence

- [Python before flamegraph](docs/flamegraphs/python315-before.svg)
- [Python after flamegraph](docs/flamegraphs/python315-after.svg)
- [Rust before flamegraph](docs/flamegraphs/rust-before.svg)
- [Rust after flamegraph](docs/flamegraphs/rust-after.svg)

## Verification

The release passed the full cross-platform Python matrix, Rust formatting and Clippy checks, 48 Rust unit tests, 421 Python tests with exactly 100% statement coverage, and install tests against the published Rust 0.4.2 wheels.


# json2xml_rs 0.4.2

Released 2026-07-15.
Expand Down
2 changes: 1 addition & 1 deletion json2xml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__author__ = """Vinit Kumar"""
__email__ = "mail@vinitkumar.me"
__version__ = "6.4.0"
__version__ = "6.5.0"
2 changes: 1 addition & 1 deletion lat.md/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Runtime dependencies are declared in `pyproject.toml` and mirrored by `uv.lock`;

GitHub Actions workflows run with read-only tokens by default and use full SHA pins so third-party action updates are explicit.

The `.github/workflows/` files declare the minimum `permissions:` scopes needed by each workflow, with CodeQL retaining `security-events: write` for result upload. Action references are pinned to immutable commits with the upstream tag retained in comments for reviewability, and `.github/dependabot.yml` checks the `github-actions` ecosystem weekly so those pins do not silently age. The Python test matrix pins its PyPy 3.11 job to an explicit PyPy release so CI keeps exercising the intended CPython 3.11.15-compatible runtime instead of silently drifting with runner cache updates. It also exercises regular CPython 3.15.0b3 while leaving that beta's free-threaded builds out of CI until the runner support is less brittle.
The `.github/workflows/` files declare the minimum `permissions:` scopes needed by each workflow, with CodeQL retaining `security-events: write` for result upload and TestPyPI retaining `id-token: write` for explicit trusted-publishing runs. Release-branch pushes build distributions and run Twine checks; TestPyPI upload is a manual opt-in because that external registry requires separate publisher configuration. Action references are pinned to immutable commits with the upstream tag retained in comments for reviewability, and `.github/dependabot.yml` checks the `github-actions` ecosystem weekly so those pins do not silently age. The Python test matrix pins its PyPy 3.11 job to an explicit PyPy release so CI keeps exercising the intended CPython 3.11.15-compatible runtime instead of silently drifting with runner cache updates. It also exercises regular CPython 3.15.0b3 while leaving that beta's free-threaded builds out of CI until the runner support is less brittle.

Rust extension CI triggers on Rust sources, Rust integration tests, and Python fast-path files such as [[json2xml/backend_selector.py]] and [[json2xml/dicttoxml_fast.py]]. That keeps native backend tests attached to the Python dispatch code that decides whether the accelerator is used.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "json2xml"
version = "6.4.0"
version = "6.5.0"
description = "Simple Python Library to convert JSON to XML"
readme = "README.rst"
requires-python = ">=3.10"
Expand Down Expand Up @@ -50,7 +50,7 @@ dev = [
"pygments>=2.20.0",
"xmltodict>=0.12.0",
]
fast = ["json2xml-rs>=0.4.1"]
fast = ["json2xml-rs>=0.4.2"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down
Loading