pyrjson 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. pyrjson-0.2.0/.cargo/config.toml +15 -0
  2. pyrjson-0.2.0/.github/dependabot.yml +24 -0
  3. pyrjson-0.2.0/.github/instructions/leader.instructions.md +62 -0
  4. pyrjson-0.2.0/.github/instructions/python-enginner.instructions.md +39 -0
  5. pyrjson-0.2.0/.github/instructions/rust-engineer.instructions.md +60 -0
  6. pyrjson-0.2.0/.github/workflows/ci.yml +160 -0
  7. pyrjson-0.2.0/.github/workflows/perf.yml +96 -0
  8. pyrjson-0.2.0/.github/workflows/wheels.yml +236 -0
  9. pyrjson-0.2.0/.gitignore +17 -0
  10. pyrjson-0.2.0/.python-version +1 -0
  11. pyrjson-0.2.0/.vscode/settings.json +7 -0
  12. pyrjson-0.2.0/AGENTS.md +29 -0
  13. pyrjson-0.2.0/CHANGELOG.md +97 -0
  14. pyrjson-0.2.0/CLAUDE.md +130 -0
  15. pyrjson-0.2.0/CONTRIBUTING.md +118 -0
  16. pyrjson-0.2.0/Cargo.lock +162 -0
  17. pyrjson-0.2.0/Cargo.toml +45 -0
  18. pyrjson-0.2.0/LICENSE +21 -0
  19. pyrjson-0.2.0/Makefile +59 -0
  20. pyrjson-0.2.0/PKG-INFO +419 -0
  21. pyrjson-0.2.0/README.md +383 -0
  22. pyrjson-0.2.0/SECURITY.md +45 -0
  23. pyrjson-0.2.0/benches/bulk_benchmark.py +113 -0
  24. pyrjson-0.2.0/benches/comprehensive_benchmark.py +227 -0
  25. pyrjson-0.2.0/benches/corpus_benchmark.py +160 -0
  26. pyrjson-0.2.0/benches/demo.py +74 -0
  27. pyrjson-0.2.0/benches/examples_benchmark.py +595 -0
  28. pyrjson-0.2.0/benches/fetch_corpus.sh +38 -0
  29. pyrjson-0.2.0/benches/large_benchmark.py +23 -0
  30. pyrjson-0.2.0/benches/make_charts.py +216 -0
  31. pyrjson-0.2.0/benches/make_demo_svg.py +75 -0
  32. pyrjson-0.2.0/benches/make_showcase_charts.py +261 -0
  33. pyrjson-0.2.0/benches/perf_gate.py +82 -0
  34. pyrjson-0.2.0/benches/prod_workloads.py +337 -0
  35. pyrjson-0.2.0/benches/production_benchmark.py +939 -0
  36. pyrjson-0.2.0/benches/profile_overhead.py +179 -0
  37. pyrjson-0.2.0/benches/python_benchmark.py +133 -0
  38. pyrjson-0.2.0/benches/showcase.py +367 -0
  39. pyrjson-0.2.0/build.rs +43 -0
  40. pyrjson-0.2.0/docs/ASYNC.md +190 -0
  41. pyrjson-0.2.0/docs/PERFORMANCE_REVIEW.md +267 -0
  42. pyrjson-0.2.0/docs/PRODUCTION_READINESS.md +404 -0
  43. pyrjson-0.2.0/docs/SHOWCASE.md +140 -0
  44. pyrjson-0.2.0/docs/examples-benchmark-results.json +1028 -0
  45. pyrjson-0.2.0/docs/guides/faster-json-in-python-services.md +120 -0
  46. pyrjson-0.2.0/docs/img/architecture-dark.svg +80 -0
  47. pyrjson-0.2.0/docs/img/architecture-light.svg +80 -0
  48. pyrjson-0.2.0/docs/img/benchmark-results.json +580 -0
  49. pyrjson-0.2.0/docs/img/demo.svg +15 -0
  50. pyrjson-0.2.0/docs/img/headline-dark.svg +19 -0
  51. pyrjson-0.2.0/docs/img/headline-light.svg +19 -0
  52. pyrjson-0.2.0/docs/img/showcase-dark.svg +86 -0
  53. pyrjson-0.2.0/docs/img/showcase-light.svg +86 -0
  54. pyrjson-0.2.0/docs/img/social-preview.png +0 -0
  55. pyrjson-0.2.0/docs/img/social-preview.svg +15 -0
  56. pyrjson-0.2.0/docs/img/vs-orjson-dark.svg +83 -0
  57. pyrjson-0.2.0/docs/img/vs-orjson-light.svg +83 -0
  58. pyrjson-0.2.0/docs/showcase-results.json +271 -0
  59. pyrjson-0.2.0/examples/README.md +122 -0
  60. pyrjson-0.2.0/examples/codec.py +545 -0
  61. pyrjson-0.2.0/examples/django_json.py +208 -0
  62. pyrjson-0.2.0/examples/fastapi_app.py +261 -0
  63. pyrjson-0.2.0/examples/flask_json.py +146 -0
  64. pyrjson-0.2.0/examples/json_logging.py +367 -0
  65. pyrjson-0.2.0/llms.txt +40 -0
  66. pyrjson-0.2.0/pyproject.toml +66 -0
  67. pyrjson-0.2.0/rjson.pyi +88 -0
  68. pyrjson-0.2.0/scripts/build_pgo.sh +87 -0
  69. pyrjson-0.2.0/scripts/ci_build_pgo_container.sh +41 -0
  70. pyrjson-0.2.0/scripts/pgo_train.py +253 -0
  71. pyrjson-0.2.0/scripts/run_tests.sh +21 -0
  72. pyrjson-0.2.0/scripts/test_aarch64_qemu.sh +33 -0
  73. pyrjson-0.2.0/src/compat.rs +100 -0
  74. pyrjson-0.2.0/src/entry.rs +523 -0
  75. pyrjson-0.2.0/src/lemire.rs +791 -0
  76. pyrjson-0.2.0/src/lib.rs +32 -0
  77. pyrjson-0.2.0/src/native.rs +805 -0
  78. pyrjson-0.2.0/src/parser.rs +2226 -0
  79. pyrjson-0.2.0/src/ser.rs +3761 -0
  80. pyrjson-0.2.0/src/stack.rs +164 -0
  81. pyrjson-0.2.0/tests/test_dumps.py +969 -0
  82. pyrjson-0.2.0/tests/test_examples.py +919 -0
  83. pyrjson-0.2.0/tests/test_format.py +175 -0
  84. pyrjson-0.2.0/tests/test_fuzz.py +246 -0
  85. pyrjson-0.2.0/tests/test_hardening.py +349 -0
  86. pyrjson-0.2.0/tests/test_keys.py +290 -0
  87. pyrjson-0.2.0/tests/test_lenient.py +303 -0
  88. pyrjson-0.2.0/tests/test_native.py +745 -0
  89. pyrjson-0.2.0/tests/test_rjson.py +1285 -0
  90. pyrjson-0.2.0/uv.lock +150 -0
@@ -0,0 +1,15 @@
1
+ # Build configuration.
2
+ #
3
+ # x86_64 targets x86-64-v2 (SSE4.2/SSSE3/POPCNT, every x86_64 CPU since ~2009),
4
+ # like orjson's wheels. Do NOT use target-cpu=native: wheels built that way
5
+ # crash with SIGILL on older CPUs, and on the benchmark Xeon, BMI2 codegen
6
+ # (enabled by native / x86-64-v3) made zmij float formatting ~1.6x slower
7
+ # (33ns vs 20ns per float in dumps).
8
+ #
9
+ # One cfg entry covers every x86_64 target (linux-gnu, linux-musl, darwin,
10
+ # windows-msvc/gnu). Setting RUSTFLAGS in the environment silently replaces
11
+ # these flags; pass extra flags via CARGO_TARGET_<TRIPLE>_RUSTFLAGS, which
12
+ # cargo merges with them (scripts/build_pgo.sh does this).
13
+
14
+ [target.'cfg(target_arch = "x86_64")']
15
+ rustflags = ["-C", "target-cpu=x86-64-v2"]
@@ -0,0 +1,24 @@
1
+ # Weekly update PRs. Actions are pinned to commit SHAs; Dependabot bumps the
2
+ # SHA and the version comment together. Grouped to keep the PR count low.
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: github-actions
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
9
+ groups:
10
+ actions:
11
+ patterns: ["*"]
12
+
13
+ - package-ecosystem: cargo
14
+ directory: /
15
+ schedule:
16
+ interval: weekly
17
+ groups:
18
+ cargo-minor:
19
+ update-types: [minor, patch]
20
+ # PyO3 upgrades touch the doc-hidden trampoline API (src/entry.rs) and
21
+ # need the version checks in CLAUDE.md; take them deliberately.
22
+ ignore:
23
+ - dependency-name: pyo3
24
+ update-types: [version-update:semver-major, version-update:semver-minor]
@@ -0,0 +1,62 @@
1
+ ---
2
+ applyTo: '**/*.md'
3
+ ---
4
+ # AI Coding Leader Instructions for Python-Rust Integration Project
5
+
6
+ ## Project Overview
7
+ This project integrates Python and Rust, using Rust for performance-critical operations while providing Pythonic interfaces. The core functionality is implemented in Rust with Python bindings.
8
+
9
+ ## Technology Stack
10
+ - **Rust**: Core implementation
11
+ - **Python**: User-facing API
12
+ - **PyO3**: Python bindings for Rust
13
+ - **Maturin**: Build system for Python extensions written in Rust
14
+
15
+ ## Coding Standards
16
+
17
+ ### Rust Standards
18
+ - Follow Rust idiomatic practices and official style guide
19
+ - Use 2021 edition of Rust
20
+ - Leverage strong typing and ownership system
21
+ - Use Result/Option for error handling, avoid panics
22
+ - Format code with `rustfmt` and lint with `clippy`
23
+ - Document all public APIs with rustdoc
24
+
25
+ ### Python Standards
26
+ - Follow PEP 8 style guidelines
27
+ - Use type hints (PEP 484)
28
+ - Format with Black and check with flake8
29
+ - Write docstrings in Google style
30
+ - Target Python 3.7+ compatibility
31
+
32
+ ## Integration Guidelines
33
+ - Create Pythonic APIs that hide Rust implementation details
34
+ - Handle memory management carefully at language boundaries
35
+ - Convert between Rust and Python types safely
36
+ - Propagate errors appropriately between languages
37
+
38
+ ## Project Structure
39
+ - `/src/`: Rust implementation
40
+ - `/python/`: Python interface code
41
+ - `/tests/`: Test suites for both languages
42
+ - `/examples/`: Example usage
43
+ - `/docs/`: Documentation
44
+ - `/benches/`: Performance benchmarks
45
+
46
+ ## Testing Requirements
47
+ - Unit tests for both languages
48
+ - Integration tests across language boundary
49
+ - Benchmark performance-critical code
50
+ - Test in CI pipeline
51
+
52
+ ## Documentation
53
+ - Document APIs in both languages
54
+ - Include examples and usage patterns
55
+ - Explain performance characteristics
56
+ - Keep README updated with installation and usage instructions
57
+
58
+ ## Performance Considerations
59
+ - Profile code to identify bottlenecks
60
+ - Minimize Python/Rust boundary crossings
61
+ - Consider parallelism for CPU-bound operations
62
+ - Benchmark critical paths
@@ -0,0 +1,39 @@
1
+ ---
2
+ applyTo: '**/*.py'
3
+ ---
4
+ # Senior Python Engineer Coding Standards
5
+
6
+ ## General Approach
7
+ - Write code as a senior Python engineer would: prioritize readability, maintainability, and efficiency
8
+ - Use Python 3.11+ syntax and features when appropriate
9
+ - Follow a systematic problem-solving approach: understand requirements, plan implementation, code, test, and refactor
10
+ - Before coding, outline a clear strategy considering edge cases, performance implications, and maintainability
11
+
12
+ ## Code Style & Organization
13
+ - Follow PEP 8 style guidelines consistently
14
+ - Use meaningful variable/function names that convey purpose
15
+ - Organize code into logical modules and packages
16
+ - Limit function/method size to maintain readability (typically under 50 lines)
17
+ - Use appropriate design patterns where they simplify the code
18
+
19
+ ## Documentation & Type Hints
20
+ - Write clear, concise docstrings for all public functions, classes, and modules
21
+ - Use type hints consistently to improve code understanding and static analysis
22
+ - Include examples in docstrings for complex functions
23
+ - Add inline comments only for non-obvious code sections
24
+
25
+ ## Best Practices
26
+ - Prefer built-in functions and standard library modules over third-party packages for simple tasks
27
+ - Write idiomatic Python (e.g., use list comprehensions, generators where appropriate)
28
+ - Handle errors gracefully using try/except with specific exception types
29
+ - Use context managers for resource management
30
+ - Implement appropriate logging rather than print statements
31
+ - Write modular, reusable, and testable code
32
+ - Practice defensive programming for robust error handling
33
+
34
+ ## Performance & Security
35
+ - Avoid premature optimization; focus on correct and clear code first
36
+ - Be mindful of performance implications for large datasets
37
+ - Use appropriate data structures for the problem at hand
38
+ - Validate all inputs, especially in public-facing interfaces
39
+ - Follow security best practices when handling sensitive data
@@ -0,0 +1,60 @@
1
+ ---
2
+ applyTo: '**/*.rs'
3
+ ---
4
+ # Senior Rust Engineering Guidelines
5
+
6
+ ## Code Style and Organization
7
+ - Follow the official Rust style guide and idioms from rust-lang/rust
8
+ - Use snake_case for variables and functions, CamelCase for types and traits
9
+ - Organize code with clear module hierarchy and separation of concerns
10
+ - Keep functions focused and concise (ideally under 50 lines)
11
+ - Use meaningful variable and function names that convey intent
12
+
13
+ ## Safety and Error Handling
14
+ - Prefer Result over panic/unwrap in public APIs
15
+ - Use `?` operator for clean error propagation
16
+ - Implement custom error types for libraries using thiserror or similar
17
+ - Handle all error cases explicitly; avoid ignoring errors
18
+ - Use `#[must_use]` for Results that shouldn't be ignored
19
+
20
+ ## Performance
21
+ - Minimize heap allocations where possible
22
+ - Consider using Cow<T> for flexible ownership
23
+ - Use iterators and avoid unnecessary collecting
24
+ - Profile before optimizing; focus on measured bottlenecks
25
+ - Use appropriate data structures for the task (e.g., HashMap vs BTreeMap)
26
+
27
+ ## Memory Management
28
+ - Leverage Rust's ownership system; avoid unnecessary Rc/Arc
29
+ - Minimize use of unsafe code; document and isolate when necessary
30
+ - Prefer references over smart pointers when appropriate
31
+ - Be explicit about lifetimes when necessary for clarity
32
+
33
+ ## Documentation
34
+ - Document all public APIs with rustdoc comments
35
+ - Include examples in documentation for complex functionality
36
+ - Document SAFETY considerations for unsafe functions
37
+ - Add comments for complex algorithms or non-obvious implementations
38
+
39
+ ## Testing
40
+ - Write unit tests for all public functions
41
+ - Include integration tests for major features
42
+ - Use property-based testing where appropriate (e.g., proptest)
43
+ - Test edge cases and error paths explicitly
44
+
45
+ ## Dependencies
46
+ - Be conservative with dependencies; evaluate their maintenance status
47
+ - Prefer crates from the rust-lang org when available
48
+ - Consider MSRV (Minimum Supported Rust Version) requirements
49
+
50
+ ## Async Code
51
+ - Use appropriate executor for the project (tokio, async-std, etc.)
52
+ - Follow structured concurrency patterns
53
+ - Be mindful of cancellation and resource cleanup
54
+ - Document blocking operations clearly
55
+
56
+ ## General
57
+ - Prefer immutable variables (let vs let mut)
58
+ - Use strong typing over runtime checks
59
+ - Leverage the type system to prevent invalid states
60
+ - Follow API design principles from the Rust API Guidelines
@@ -0,0 +1,160 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ci-${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ env:
17
+ CARGO_TERM_COLOR: always
18
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
19
+
20
+ # Never set RUSTFLAGS in this workflow: it replaces the target rustflags from
21
+ # .cargo/config.toml (target-cpu=x86-64-v2). Extra flags go into
22
+ # CARGO_TARGET_<TRIPLE>_RUSTFLAGS, which cargo merges with the config.
23
+
24
+ jobs:
25
+ lint:
26
+ runs-on: ubuntu-24.04
27
+ steps:
28
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
29
+ with:
30
+ persist-credentials: false
31
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
32
+ with:
33
+ python-version: "3.13"
34
+ - uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
35
+ with:
36
+ toolchain: stable
37
+ components: clippy
38
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
39
+ # Warnings are reported, not fatal (two pre-existing clippy warnings in
40
+ # src/ser.rs). `cargo fmt --check` is not enforced yet: src/entry.rs and
41
+ # src/parser.rs are not rustfmt-clean.
42
+ - run: cargo clippy --release --all-targets
43
+ env:
44
+ PYO3_PYTHON: python
45
+
46
+ test:
47
+ name: test ${{ matrix.os }} py${{ matrix.python }}${{ matrix.no-avx512 && ' no-avx512' || '' }}
48
+ runs-on: ${{ matrix.os }}
49
+ strategy:
50
+ fail-fast: false
51
+ matrix:
52
+ # requires-python >=3.10; PyO3 0.29 supports up to 3.14 (3.14 str access: src/compat.rs).
53
+ os: [ubuntu-24.04]
54
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
55
+ include:
56
+ # SSE2/AVX2 escape kernels (the runner may have AVX-512).
57
+ - os: ubuntu-24.04
58
+ python: "3.13"
59
+ no-avx512: true
60
+ # aarch64: SWAR / non-x86 fallback paths, pre- and post-3.12 layouts.
61
+ - os: ubuntu-24.04-arm
62
+ python: "3.10"
63
+ - os: ubuntu-24.04-arm
64
+ python: "3.13"
65
+ - os: macos-14
66
+ python: "3.13"
67
+ # Windows links private C-API symbols via the full import library
68
+ # (build.rs); 3.10 covers _PyDict_NewPresized, 3.13 _PyDict_FromItems.
69
+ - os: windows-2022
70
+ python: "3.10"
71
+ - os: windows-2022
72
+ python: "3.13"
73
+ defaults:
74
+ run:
75
+ shell: bash
76
+ steps:
77
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
78
+ with:
79
+ persist-credentials: false
80
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
81
+ with:
82
+ python-version: ${{ matrix.python }}
83
+ - uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
84
+ with:
85
+ toolchain: stable
86
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
87
+ with:
88
+ key: py${{ matrix.python }}-${{ matrix.no-avx512 && 'no-avx512' || 'default' }}
89
+ - name: Disable the AVX-512 kernel
90
+ if: matrix.no-avx512
91
+ run: echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=--cfg rjson_no_avx512" >> "$GITHUB_ENV"
92
+ # fastapi + httpx: tests/test_examples.py skips its FastAPI tests without them;
93
+ # orjson: tests/test_native.py compares against it (skipped without it);
94
+ # tzdata: ZoneInfo zones on Windows, which has no system tz database.
95
+ - run: python -m pip install "maturin>=1.8,<2" pytest fastapi httpx orjson tzdata
96
+ - name: Build wheel
97
+ run: maturin build --release -i python -o dist
98
+ - name: Install wheel
99
+ run: python -m pip install --no-index --find-links dist pyrjson
100
+ - run: python -m pytest tests -q
101
+
102
+ # musl (Alpine): the musllinux wheels' platform. Its pthread_getattr_np
103
+ # reports the main thread's stack differently from glibc (src/stack.rs);
104
+ # rjson 0.1.0's musllinux wheel builds failed on exactly that.
105
+ musl:
106
+ name: test musl (Alpine) py3.13
107
+ runs-on: ubuntu-24.04
108
+ container: python:3.13-alpine
109
+ steps:
110
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
111
+ with:
112
+ persist-credentials: false
113
+ - name: Install the Rust toolchain
114
+ run: |
115
+ apk add --no-cache build-base curl patchelf
116
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
117
+ echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
118
+ - run: python -m pip install "maturin>=1.8,<2" pytest orjson
119
+ - name: Build wheel
120
+ run: maturin build --release -i python -o dist
121
+ - name: Install wheel
122
+ run: python -m pip install --no-index --find-links dist pyrjson
123
+ - run: python -m pytest tests -q
124
+
125
+ # The extension built with AddressSanitizer (nightly rustc), run under a
126
+ # normal CPython with the ASan runtime preloaded. PYTHONMALLOC=malloc puts
127
+ # Python objects (the bytes/str rjson writes into) on ASan's allocator, so
128
+ # out-of-bounds reads/writes and use-after-free are reported; leak checking
129
+ # is off because CPython keeps objects alive until exit on purpose. The fuzz
130
+ # suite runs with more cases here.
131
+ sanitize:
132
+ name: sanitize (ASan) py3.13
133
+ runs-on: ubuntu-24.04
134
+ steps:
135
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
136
+ with:
137
+ persist-credentials: false
138
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
139
+ with:
140
+ python-version: "3.13"
141
+ - uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
142
+ with:
143
+ toolchain: nightly
144
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
145
+ with:
146
+ key: asan
147
+ - run: python -m pip install "maturin>=1.8,<2" pytest fastapi httpx orjson
148
+ - name: Build wheel with AddressSanitizer
149
+ env:
150
+ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: -Zsanitizer=address
151
+ run: maturin build --release --target x86_64-unknown-linux-gnu -i python -o dist
152
+ - name: Install wheel
153
+ run: python -m pip install --no-index --find-links dist pyrjson
154
+ - name: Tests and fuzzing under ASan
155
+ env:
156
+ ASAN_OPTIONS: detect_leaks=0:abort_on_error=1
157
+ PYTHONMALLOC: malloc
158
+ RJSON_SANITIZER: asan
159
+ RJSON_FUZZ_ITERS: "5000"
160
+ run: LD_PRELOAD="$(gcc -print-file-name=libasan.so)" python -m pytest tests -q
@@ -0,0 +1,96 @@
1
+ name: Perf gate
2
+
3
+ # Opt-in performance regression gate (shared runners are noisy): runs on PRs
4
+ # labelled `perf` and on manual dispatch. Builds base and head (plain release,
5
+ # no PGO) in the same job, runs benches/corpus_benchmark.py for both,
6
+ # interleaved, and fails if any rjson/orjson geomean (loads, dumps,
7
+ # dumps_str) is more than 5% worse than base. The ratio to orjson, timed in
8
+ # the same process, cancels most runner speed drift.
9
+
10
+ on:
11
+ pull_request:
12
+ types: [opened, synchronize, reopened, labeled]
13
+ workflow_dispatch:
14
+ inputs:
15
+ base:
16
+ description: Base ref to compare against
17
+ default: main
18
+ threshold:
19
+ description: Max allowed geomean regression (0.05 = 5%)
20
+ default: "0.05"
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ concurrency:
26
+ group: perf-${{ github.ref }}
27
+ cancel-in-progress: true
28
+
29
+ env:
30
+ CARGO_TERM_COLOR: always
31
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
32
+ ROUNDS: "5"
33
+
34
+ jobs:
35
+ perf:
36
+ if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'perf')
37
+ name: perf py${{ matrix.python }}
38
+ runs-on: ubuntu-24.04
39
+ timeout-minutes: 60
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ # 3.11 exercises the GC-pause path, 3.13 the like-for-like parser.
44
+ python: ["3.11", "3.13"]
45
+ steps:
46
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
47
+ with:
48
+ persist-credentials: false
49
+ path: head
50
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
51
+ with:
52
+ persist-credentials: false
53
+ path: base
54
+ ref: ${{ github.event.pull_request.base.sha || inputs.base }}
55
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
56
+ with:
57
+ python-version: ${{ matrix.python }}
58
+ - uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master
59
+ with:
60
+ toolchain: stable
61
+ - name: Fetch corpora (sha256-pinned)
62
+ run: head/benches/fetch_corpus.sh "$RUNNER_TEMP/corpus"
63
+ - name: Build base and head
64
+ run: |
65
+ python -m pip install "maturin>=1.8,<2"
66
+ for side in base head; do
67
+ (cd "$side" && maturin build --release -i python -o "$RUNNER_TEMP/wheels-$side")
68
+ python -m venv "$RUNNER_TEMP/venv-$side"
69
+ "$RUNNER_TEMP/venv-$side/bin/python" -m pip install -q orjson
70
+ # Install by file, not by name: the base ref may predate the
71
+ # rjson -> pyrjson distribution rename (one wheel per directory).
72
+ "$RUNNER_TEMP/venv-$side/bin/python" -m pip install -q --no-index "$RUNNER_TEMP/wheels-$side"/*.whl
73
+ done
74
+ - name: Benchmark (interleaved)
75
+ run: |
76
+ mkdir -p results
77
+ for r in $(seq 1 "$ROUNDS"); do
78
+ # alternate which side goes first to cancel ordering effects
79
+ if (( r % 2 )); then order="base head"; else order="head base"; fi
80
+ for side in $order; do
81
+ # head's benchmark script for both sides (base may predate --output-json)
82
+ "$RUNNER_TEMP/venv-$side/bin/python" head/benches/corpus_benchmark.py \
83
+ --data "$RUNNER_TEMP/corpus" --repeat 5 --output-json "results/$side-$r.json" > /dev/null
84
+ done
85
+ done
86
+ - name: Compare
87
+ run: |
88
+ python head/benches/perf_gate.py --threshold "${THRESHOLD}" \
89
+ --base results/base-*.json --head results/head-*.json
90
+ env:
91
+ THRESHOLD: ${{ inputs.threshold || '0.05' }}
92
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
93
+ if: always()
94
+ with:
95
+ name: perf-results-py${{ matrix.python }}
96
+ path: results/