Skip to content

Commit 0584352

Browse files
committed
feat: add native Rust extension for 29x faster performance (v6.0.0)
This major release introduces an optional native Rust extension built with PyO3 that provides approximately 29x faster JSON to XML conversion compared to pure Python. Performance improvements: - Small JSON (47 bytes): 33x faster - Medium JSON (3.2KB): 28x faster - Large JSON (32KB): 30x faster - Very Large JSON (323KB): 29x faster New features: - Optional Rust extension (json2xml-rs) via PyO3 - dicttoxml_fast module with automatic backend selection - Seamless fallback to pure Python when Rust is unavailable - Pre-built wheels for Linux, macOS, and Windows New files: - rust/ - PyO3 Rust extension source code - json2xml/dicttoxml_fast.py - Auto-selecting wrapper module - tests/test_rust_dicttoxml.py - 65 comprehensive tests - benchmark_rust.py - Performance comparison script - .github/workflows/build-rust-wheels.yml - Wheel build CI - .github/workflows/rust-ci.yml - Rust code quality CI Documentation: - Updated README with Rust extension usage and benchmarks - Updated CONTRIBUTING with Rust development guide - Added HISTORY entry for v6.0.0
1 parent b714ee6 commit 0584352

File tree

16 files changed

+2315
-174
lines changed

16 files changed

+2315
-174
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
name: Build and Publish Rust Extension (json2xml-rs)
2+
3+
on:
4+
push:
5+
tags:
6+
- 'rust-v*' # Trigger on tags like rust-v0.1.0
7+
workflow_dispatch: # Allow manual trigger
8+
inputs:
9+
publish:
10+
description: 'Publish to PyPI'
11+
required: false
12+
default: 'false'
13+
type: boolean
14+
15+
env:
16+
PACKAGE_NAME: json2xml_rs
17+
PYTHON_VERSION: '3.12'
18+
19+
jobs:
20+
# Build wheels for Linux
21+
linux:
22+
runs-on: ubuntu-latest
23+
strategy:
24+
matrix:
25+
target: [x86_64, aarch64]
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- uses: actions/setup-python@v5
30+
with:
31+
python-version: ${{ env.PYTHON_VERSION }}
32+
33+
- name: Build wheels
34+
uses: PyO3/maturin-action@v1
35+
with:
36+
target: ${{ matrix.target }}
37+
args: --release --out dist --find-interpreter
38+
sccache: 'true'
39+
manylinux: auto
40+
working-directory: rust
41+
42+
- name: Upload wheels
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: wheels-linux-${{ matrix.target }}
46+
path: rust/dist
47+
48+
# Build wheels for Windows
49+
windows:
50+
runs-on: windows-latest
51+
strategy:
52+
matrix:
53+
target: [x64]
54+
steps:
55+
- uses: actions/checkout@v4
56+
57+
- uses: actions/setup-python@v5
58+
with:
59+
python-version: ${{ env.PYTHON_VERSION }}
60+
architecture: ${{ matrix.target }}
61+
62+
- name: Build wheels
63+
uses: PyO3/maturin-action@v1
64+
with:
65+
target: ${{ matrix.target == 'x64' && 'x86_64-pc-windows-msvc' || 'i686-pc-windows-msvc' }}
66+
args: --release --out dist --find-interpreter
67+
sccache: 'true'
68+
working-directory: rust
69+
70+
- name: Upload wheels
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: wheels-windows-${{ matrix.target }}
74+
path: rust/dist
75+
76+
# Build wheels for macOS
77+
macos:
78+
runs-on: macos-latest
79+
strategy:
80+
matrix:
81+
target: [x86_64, aarch64]
82+
steps:
83+
- uses: actions/checkout@v4
84+
85+
- uses: actions/setup-python@v5
86+
with:
87+
python-version: ${{ env.PYTHON_VERSION }}
88+
89+
- name: Build wheels
90+
uses: PyO3/maturin-action@v1
91+
with:
92+
target: ${{ matrix.target == 'x86_64' && 'x86_64-apple-darwin' || 'aarch64-apple-darwin' }}
93+
args: --release --out dist --find-interpreter
94+
sccache: 'true'
95+
working-directory: rust
96+
97+
- name: Upload wheels
98+
uses: actions/upload-artifact@v4
99+
with:
100+
name: wheels-macos-${{ matrix.target }}
101+
path: rust/dist
102+
103+
# Build source distribution
104+
sdist:
105+
runs-on: ubuntu-latest
106+
steps:
107+
- uses: actions/checkout@v4
108+
109+
- name: Build sdist
110+
uses: PyO3/maturin-action@v1
111+
with:
112+
command: sdist
113+
args: --out dist
114+
working-directory: rust
115+
116+
- name: Upload sdist
117+
uses: actions/upload-artifact@v4
118+
with:
119+
name: wheels-sdist
120+
path: rust/dist
121+
122+
# Publish to PyPI
123+
publish:
124+
name: Publish to PyPI
125+
runs-on: ubuntu-latest
126+
needs: [linux, windows, macos, sdist]
127+
if: startsWith(github.ref, 'refs/tags/rust-v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')
128+
environment:
129+
name: pypi
130+
url: https://pypi.org/project/json2xml-rs/
131+
permissions:
132+
id-token: write # Required for trusted publishing
133+
134+
steps:
135+
- name: Download all artifacts
136+
uses: actions/download-artifact@v4
137+
with:
138+
pattern: wheels-*
139+
path: dist
140+
merge-multiple: true
141+
142+
- name: List artifacts
143+
run: ls -la dist/
144+
145+
- name: Publish to PyPI
146+
uses: pypa/gh-action-pypi-publish@release/v1
147+
with:
148+
# For trusted publishing, no token needed if configured on PyPI
149+
# Otherwise use: password: ${{ secrets.PYPI_API_TOKEN_RUST }}
150+
skip-existing: true
151+
152+
# Test the wheels
153+
test:
154+
name: Test wheels
155+
runs-on: ${{ matrix.os }}
156+
needs: [linux, windows, macos]
157+
strategy:
158+
fail-fast: false
159+
matrix:
160+
os: [ubuntu-latest, windows-latest, macos-latest]
161+
python-version: ['3.10', '3.11', '3.12', '3.13']
162+
steps:
163+
- uses: actions/checkout@v4
164+
165+
- uses: actions/setup-python@v5
166+
with:
167+
python-version: ${{ matrix.python-version }}
168+
169+
- name: Download wheels
170+
uses: actions/download-artifact@v4
171+
with:
172+
pattern: wheels-*
173+
path: dist
174+
merge-multiple: true
175+
176+
- name: Install wheel
177+
run: |
178+
pip install --find-links dist json2xml_rs
179+
pip install pytest
180+
181+
- name: Test import
182+
run: |
183+
python -c "from json2xml_rs import dicttoxml; print('Import successful!')"
184+
python -c "from json2xml_rs import dicttoxml; result = dicttoxml({'test': 'value'}); print(result.decode())"
185+
186+
- name: Run tests
187+
run: |
188+
pip install -e .
189+
pytest tests/test_rust_dicttoxml.py -v

.github/workflows/rust-ci.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Rust Extension CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
paths:
7+
- 'rust/**'
8+
- 'tests/test_rust_dicttoxml.py'
9+
- '.github/workflows/rust-ci.yml'
10+
pull_request:
11+
branches: [master, main]
12+
paths:
13+
- 'rust/**'
14+
- 'tests/test_rust_dicttoxml.py'
15+
- '.github/workflows/rust-ci.yml'
16+
17+
env:
18+
CARGO_TERM_COLOR: always
19+
20+
jobs:
21+
rust-lint:
22+
name: Rust Lint & Format
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Install Rust
28+
uses: dtolnay/rust-action@stable
29+
with:
30+
components: rustfmt, clippy
31+
32+
- name: Check formatting
33+
working-directory: rust
34+
run: cargo fmt --check
35+
36+
- name: Run clippy
37+
working-directory: rust
38+
run: cargo clippy --all-targets --all-features -- -D warnings
39+
40+
rust-test:
41+
name: Build & Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
42+
runs-on: ${{ matrix.os }}
43+
strategy:
44+
fail-fast: false
45+
matrix:
46+
os: [ubuntu-latest, macos-latest, windows-latest]
47+
python-version: ['3.10', '3.11', '3.12', '3.13']
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Set up Python ${{ matrix.python-version }}
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: ${{ matrix.python-version }}
55+
56+
- name: Install Rust
57+
uses: dtolnay/rust-action@stable
58+
59+
- name: Install maturin
60+
run: pip install maturin
61+
62+
- name: Build Rust extension
63+
working-directory: rust
64+
run: maturin build --release
65+
66+
- name: Install the wheel
67+
shell: bash
68+
run: |
69+
pip install rust/target/wheels/*.whl
70+
71+
- name: Install test dependencies
72+
run: |
73+
pip install pytest defusedxml
74+
pip install -e .
75+
76+
- name: Verify import
77+
run: |
78+
python -c "from json2xml_rs import dicttoxml; print('Rust extension loaded!')"
79+
python -c "from json2xml.dicttoxml_fast import get_backend; print(f'Backend: {get_backend()}')"
80+
81+
- name: Run Rust-specific tests
82+
run: pytest tests/test_rust_dicttoxml.py -v
83+
84+
- name: Run full test suite
85+
run: pytest tests/ -v --ignore=tests/test_cli.py
86+
87+
benchmark:
88+
name: Performance Benchmark
89+
runs-on: ubuntu-latest
90+
steps:
91+
- uses: actions/checkout@v4
92+
93+
- name: Set up Python
94+
uses: actions/setup-python@v5
95+
with:
96+
python-version: '3.12'
97+
98+
- name: Install Rust
99+
uses: dtolnay/rust-action@stable
100+
101+
- name: Install maturin
102+
run: pip install maturin
103+
104+
- name: Build Rust extension
105+
working-directory: rust
106+
run: maturin build --release
107+
108+
- name: Install dependencies
109+
run: |
110+
pip install rust/target/wheels/*.whl
111+
pip install -e .
112+
113+
- name: Run benchmark
114+
run: python benchmark_rust.py

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,9 @@ dmypy.json
129129
.pyre/
130130

131131
.idea/
132+
133+
# Rust
134+
rust/target/
135+
Cargo.lock
136+
*.rlib
137+
*.rmeta

0 commit comments

Comments
 (0)