rqx 0.1.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 (87) hide show
  1. rqx-0.1.0/.github/dependabot.yml +40 -0
  2. rqx-0.1.0/.github/workflows/CI.yml +214 -0
  3. rqx-0.1.0/.gitignore +98 -0
  4. rqx-0.1.0/CONTRIBUTING.md +86 -0
  5. rqx-0.1.0/Cargo.lock +2650 -0
  6. rqx-0.1.0/Cargo.toml +36 -0
  7. rqx-0.1.0/LICENSE +21 -0
  8. rqx-0.1.0/PKG-INFO +121 -0
  9. rqx-0.1.0/README.md +92 -0
  10. rqx-0.1.0/benchmarks/README.md +17 -0
  11. rqx-0.1.0/benchmarks/analyze_b1.py +54 -0
  12. rqx-0.1.0/benchmarks/b10_tls_handshake.py +178 -0
  13. rqx-0.1.0/benchmarks/b1_aiohttp.py +64 -0
  14. rqx-0.1.0/benchmarks/b1_httpr.py +70 -0
  15. rqx-0.1.0/benchmarks/b1_httpx.py +65 -0
  16. rqx-0.1.0/benchmarks/b1_rqx.py +64 -0
  17. rqx-0.1.0/benchmarks/b2_latency.py +157 -0
  18. rqx-0.1.0/benchmarks/b3_connection_pool.py +94 -0
  19. rqx-0.1.0/benchmarks/b4_memory.py +93 -0
  20. rqx-0.1.0/benchmarks/b5_json_parsing.py +100 -0
  21. rqx-0.1.0/benchmarks/b6_retry_overhead.py +101 -0
  22. rqx-0.1.0/benchmarks/b7_network_latency.py +131 -0
  23. rqx-0.1.0/benchmarks/b8_concurrency_sweep.py +329 -0
  24. rqx-0.1.0/benchmarks/b9_payload_sweep.py +272 -0
  25. rqx-0.1.0/benchmarks/delay_server.py +29 -0
  26. rqx-0.1.0/benchmarks/docker-compose.yaml +10 -0
  27. rqx-0.1.0/benchmarks/infra/.gitignore +13 -0
  28. rqx-0.1.0/benchmarks/infra/Pulumi.yaml +3 -0
  29. rqx-0.1.0/benchmarks/infra/README.md +97 -0
  30. rqx-0.1.0/benchmarks/infra/index.ts +275 -0
  31. rqx-0.1.0/benchmarks/infra/package-lock.json +2668 -0
  32. rqx-0.1.0/benchmarks/infra/package.json +11 -0
  33. rqx-0.1.0/benchmarks/infra/scripts/bench.sh +128 -0
  34. rqx-0.1.0/benchmarks/infra/scripts/client-setup.sh +80 -0
  35. rqx-0.1.0/benchmarks/infra/scripts/run-benches.sh +63 -0
  36. rqx-0.1.0/benchmarks/infra/scripts/server-setup.sh +37 -0
  37. rqx-0.1.0/benchmarks/infra/tsconfig.json +16 -0
  38. rqx-0.1.0/benchmarks/nginx/generate_payloads.py +58 -0
  39. rqx-0.1.0/benchmarks/nginx/nginx-host.conf +86 -0
  40. rqx-0.1.0/benchmarks/nginx/nginx.conf +28 -0
  41. rqx-0.1.0/benchmarks/nginx/response.json +63 -0
  42. rqx-0.1.0/benchmarks/plot_launch.py +147 -0
  43. rqx-0.1.0/benchmarks/run_all.sh +104 -0
  44. rqx-0.1.0/benchmarks/run_b1.sh +43 -0
  45. rqx-0.1.0/docs/launch_latency.png +0 -0
  46. rqx-0.1.0/docs/launch_memory.png +0 -0
  47. rqx-0.1.0/docs/launch_report.md +113 -0
  48. rqx-0.1.0/docs/launch_throughput.png +0 -0
  49. rqx-0.1.0/docs/report.md +164 -0
  50. rqx-0.1.0/docs/reqx_project_spec.md +928 -0
  51. rqx-0.1.0/justfile +48 -0
  52. rqx-0.1.0/pyproject.toml +46 -0
  53. rqx-0.1.0/python/rqx/__init__.py +80 -0
  54. rqx-0.1.0/python/rqx/_api.py +318 -0
  55. rqx-0.1.0/python/rqx/_types.pyi +560 -0
  56. rqx-0.1.0/python/rqx/py.typed +0 -0
  57. rqx-0.1.0/src/client.rs +1208 -0
  58. rqx-0.1.0/src/exceptions.rs +89 -0
  59. rqx-0.1.0/src/headers.rs +158 -0
  60. rqx-0.1.0/src/lib.rs +83 -0
  61. rqx-0.1.0/src/py_json.rs +124 -0
  62. rqx-0.1.0/src/request.rs +143 -0
  63. rqx-0.1.0/src/response.rs +320 -0
  64. rqx-0.1.0/src/retry.rs +174 -0
  65. rqx-0.1.0/src/runtime.rs +4 -0
  66. rqx-0.1.0/src/stream.rs +402 -0
  67. rqx-0.1.0/src/timeout.rs +112 -0
  68. rqx-0.1.0/src/transport.rs +726 -0
  69. rqx-0.1.0/src/url.rs +59 -0
  70. rqx-0.1.0/tests/conftest.py +241 -0
  71. rqx-0.1.0/tests/ssl/client_extfile.txt +2 -0
  72. rqx-0.1.0/tests/ssl/extfile.txt +2 -0
  73. rqx-0.1.0/tests/ssl/generate_certs.sh +129 -0
  74. rqx-0.1.0/tests/test_api.py +131 -0
  75. rqx-0.1.0/tests/test_async.py +633 -0
  76. rqx-0.1.0/tests/test_base_url.py +140 -0
  77. rqx-0.1.0/tests/test_compression.py +26 -0
  78. rqx-0.1.0/tests/test_encoding.py +67 -0
  79. rqx-0.1.0/tests/test_exceptions.py +100 -0
  80. rqx-0.1.0/tests/test_headers.py +93 -0
  81. rqx-0.1.0/tests/test_mtls.py +323 -0
  82. rqx-0.1.0/tests/test_response_props.py +124 -0
  83. rqx-0.1.0/tests/test_retry_config.py +139 -0
  84. rqx-0.1.0/tests/test_stream_redirects.py +91 -0
  85. rqx-0.1.0/tests/test_sync.py +886 -0
  86. rqx-0.1.0/tests/test_timeout.py +212 -0
  87. rqx-0.1.0/uv.lock +1225 -0
@@ -0,0 +1,40 @@
1
+ # Dependabot version updates.
2
+ # Docs: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
3
+ #
4
+ # Three ecosystems live in this repo:
5
+ # - pip: Python deps in pyproject.toml
6
+ # - cargo: Rust deps in Cargo.toml / Cargo.lock
7
+ # - github-actions: action versions pinned in .github/workflows/*.yml
8
+ #
9
+ # Minor + patch updates are grouped into a single PR per ecosystem per week to
10
+ # reduce churn. Major updates land as individual PRs so they can be reviewed
11
+ # in isolation. Bump `interval` to `monthly` if weekly proves too noisy.
12
+
13
+ version: 2
14
+ updates:
15
+ - package-ecosystem: "pip"
16
+ directory: "/"
17
+ schedule:
18
+ interval: "weekly"
19
+ open-pull-requests-limit: 5
20
+ groups:
21
+ python-minor-and-patch:
22
+ update-types:
23
+ - "minor"
24
+ - "patch"
25
+
26
+ - package-ecosystem: "cargo"
27
+ directory: "/"
28
+ schedule:
29
+ interval: "weekly"
30
+ open-pull-requests-limit: 5
31
+ groups:
32
+ rust-minor-and-patch:
33
+ update-types:
34
+ - "minor"
35
+ - "patch"
36
+
37
+ - package-ecosystem: "github-actions"
38
+ directory: "/"
39
+ schedule:
40
+ interval: "weekly"
@@ -0,0 +1,214 @@
1
+ # This file is autogenerated by maturin v1.12.6
2
+ # To update, run
3
+ #
4
+ # maturin generate-ci github
5
+ #
6
+ name: CI
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+ tags:
13
+ - '*'
14
+ pull_request:
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ lint:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ - uses: dtolnay/rust-toolchain@stable
26
+ with:
27
+ components: clippy
28
+ - run: cargo clippy -- -W clippy::all
29
+
30
+ test:
31
+ runs-on: ubuntu-latest
32
+ services:
33
+ httpbin:
34
+ image: kennethreitz/httpbin
35
+ ports:
36
+ - 80:80
37
+ steps:
38
+ - uses: actions/checkout@v6
39
+ - uses: actions/setup-python@v6
40
+ with:
41
+ python-version: "3.11"
42
+ - uses: astral-sh/setup-uv@v3
43
+ with:
44
+ enable-cache: true
45
+ - uses: dtolnay/rust-toolchain@stable
46
+ - run: uv sync --extra dev --frozen
47
+ - run: uv run maturin develop
48
+ - run: uv run pytest tests/ -n 8
49
+
50
+ linux:
51
+ runs-on: ${{ matrix.platform.runner }}
52
+ strategy:
53
+ matrix:
54
+ platform:
55
+ - runner: ubuntu-22.04
56
+ target: x86_64
57
+ - runner: ubuntu-22.04
58
+ target: x86
59
+ - runner: ubuntu-22.04
60
+ target: aarch64
61
+ - runner: ubuntu-22.04
62
+ target: armv7
63
+ - runner: ubuntu-22.04
64
+ target: s390x
65
+ - runner: ubuntu-22.04
66
+ target: ppc64le
67
+ steps:
68
+ - uses: actions/checkout@v6
69
+ - uses: actions/setup-python@v6
70
+ with:
71
+ python-version: 3.x
72
+ - name: Build wheels
73
+ uses: PyO3/maturin-action@v1
74
+ with:
75
+ target: ${{ matrix.platform.target }}
76
+ args: --release --out dist --find-interpreter
77
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
78
+ manylinux: 2_28
79
+ - name: Upload wheels
80
+ uses: actions/upload-artifact@v6
81
+ with:
82
+ name: wheels-linux-${{ matrix.platform.target }}
83
+ path: dist
84
+
85
+ musllinux:
86
+ runs-on: ${{ matrix.platform.runner }}
87
+ strategy:
88
+ matrix:
89
+ platform:
90
+ - runner: ubuntu-22.04
91
+ target: x86_64
92
+ - runner: ubuntu-22.04
93
+ target: x86
94
+ - runner: ubuntu-22.04
95
+ target: aarch64
96
+ - runner: ubuntu-22.04
97
+ target: armv7
98
+ steps:
99
+ - uses: actions/checkout@v6
100
+ - uses: actions/setup-python@v6
101
+ with:
102
+ python-version: 3.x
103
+ - name: Build wheels
104
+ uses: PyO3/maturin-action@v1
105
+ with:
106
+ target: ${{ matrix.platform.target }}
107
+ args: --release --out dist --find-interpreter
108
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
109
+ manylinux: musllinux_1_2
110
+ - name: Upload wheels
111
+ uses: actions/upload-artifact@v6
112
+ with:
113
+ name: wheels-musllinux-${{ matrix.platform.target }}
114
+ path: dist
115
+
116
+ windows:
117
+ runs-on: ${{ matrix.platform.runner }}
118
+ strategy:
119
+ matrix:
120
+ platform:
121
+ - runner: windows-latest
122
+ target: x64
123
+ python_arch: x64
124
+ - runner: windows-latest
125
+ target: x86
126
+ python_arch: x86
127
+ - runner: windows-11-arm
128
+ target: aarch64
129
+ python_arch: arm64
130
+ steps:
131
+ - uses: actions/checkout@v6
132
+ - uses: actions/setup-python@v6
133
+ with:
134
+ python-version: 3.13
135
+ architecture: ${{ matrix.platform.python_arch }}
136
+ - name: Build wheels
137
+ uses: PyO3/maturin-action@v1
138
+ with:
139
+ target: ${{ matrix.platform.target }}
140
+ args: --release --out dist --find-interpreter
141
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
142
+ - name: Upload wheels
143
+ uses: actions/upload-artifact@v6
144
+ with:
145
+ name: wheels-windows-${{ matrix.platform.target }}
146
+ path: dist
147
+
148
+ macos:
149
+ runs-on: ${{ matrix.platform.runner }}
150
+ strategy:
151
+ matrix:
152
+ platform:
153
+ - runner: macos-15-intel
154
+ target: x86_64
155
+ - runner: macos-latest
156
+ target: aarch64
157
+ steps:
158
+ - uses: actions/checkout@v6
159
+ - uses: actions/setup-python@v6
160
+ with:
161
+ python-version: 3.x
162
+ - name: Build wheels
163
+ uses: PyO3/maturin-action@v1
164
+ with:
165
+ target: ${{ matrix.platform.target }}
166
+ args: --release --out dist --find-interpreter
167
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
168
+ - name: Upload wheels
169
+ uses: actions/upload-artifact@v6
170
+ with:
171
+ name: wheels-macos-${{ matrix.platform.target }}
172
+ path: dist
173
+
174
+ sdist:
175
+ runs-on: ubuntu-latest
176
+ steps:
177
+ - uses: actions/checkout@v6
178
+ - name: Build sdist
179
+ uses: PyO3/maturin-action@v1
180
+ with:
181
+ command: sdist
182
+ args: --out dist
183
+ - name: Upload sdist
184
+ uses: actions/upload-artifact@v6
185
+ with:
186
+ name: wheels-sdist
187
+ path: dist
188
+
189
+ release:
190
+ name: Release
191
+ runs-on: ubuntu-latest
192
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
193
+ needs: [linux, musllinux, windows, macos, sdist]
194
+ permissions:
195
+ # Use to sign the release artifacts
196
+ id-token: write
197
+ # Used to upload release artifacts
198
+ contents: write
199
+ # Used to generate artifact attestation
200
+ attestations: write
201
+ steps:
202
+ - uses: actions/download-artifact@v7
203
+ - name: Generate artifact attestation
204
+ uses: actions/attest-build-provenance@v3
205
+ with:
206
+ subject-path: 'wheels-*/*'
207
+ - name: Merge wheel directories
208
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
209
+ run: mkdir -p dist && cp wheels-*/* dist/
210
+ - name: Publish to PyPI
211
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
212
+ uses: pypa/gh-action-pypi-publish@release/v1
213
+ with:
214
+ packages-dir: dist/
rqx-0.1.0/.gitignore ADDED
@@ -0,0 +1,98 @@
1
+ /target
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ .pytest_cache/
6
+ *.py[cod]
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ .venv/
14
+ env/
15
+ bin/
16
+ build/
17
+ develop-eggs/
18
+ dist/
19
+ eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ include/
26
+ man/
27
+ venv/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ *.dSYM
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+ pip-selfcheck.json
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .coverage
42
+ .cache
43
+ nosetests.xml
44
+ coverage.xml
45
+
46
+ # Translations
47
+ *.mo
48
+
49
+ # Mr Developer
50
+ .mr.developer.cfg
51
+ .project
52
+ .pydevproject
53
+
54
+ # Rope
55
+ .ropeproject
56
+
57
+ # Django stuff:
58
+ *.log
59
+ *.pot
60
+
61
+ .DS_Store
62
+
63
+ # Sphinx documentation
64
+ docs/_build/
65
+
66
+ # PyCharm
67
+ .idea/
68
+
69
+ # VSCode
70
+ .vscode/
71
+
72
+ # Pyenv
73
+ .python-version
74
+
75
+ # AI
76
+ .claude/
77
+ CLAUDE.md
78
+
79
+ # Benchmark stuff
80
+ benchmarks/nginx/certs/
81
+ benchmarks/nginx/*.json
82
+ docs/bench_results/
83
+
84
+ # Bench result output (commit canonical runs explicitly with `git add -f`;
85
+ # everything else stays local + S3).
86
+ benchmarks/results/
87
+ b1_results.jsonl
88
+
89
+ # Pulumi state / stack secrets
90
+ benchmarks/infra/node_modules/
91
+ benchmarks/infra/bin/
92
+ benchmarks/infra/.pulumi/
93
+
94
+ # certs stuf
95
+ *.pem
96
+ *.csr
97
+ *.srl
98
+ *.cert-gen.lock
@@ -0,0 +1,86 @@
1
+ # Contributing
2
+
3
+ ## Finding ways to help
4
+
5
+ This project started as an experiment, with the goal of learning. It evolved quickly into a genuine contender for high-concurrency HTTP handling in Python.
6
+
7
+ The goal is to be maintain feature parity with [httpx](https://github.com/encode/httpx), but with the performance of async Rust. This basically creates 2 natural places for improvements: feature parity and performance, with correctness as table stakes.
8
+
9
+ There are several tags we use to make that simple: `httpx-feature-parity` and then your run-of-the-mill tags like `bug`, `enhancement`, etc.
10
+
11
+ ## Use of AI
12
+
13
+ This project started out as learning project. I used AI to help me learn some of the basics but wrote the large majority by hand... double-edged sword. So as we advance, I encourage the use of AI for breadth and speed. It's obvious when someone is using AI to write the code for them, let's try to avoid that so that when an issue comes up, someone can speak to it without relying on their AI to go dig for them.
14
+
15
+ ### Good use cases for AI
16
+
17
+ - Getting it to run and analyze the benchmarks after a change.
18
+ - Doing a breadth-first search for alternatives.
19
+ - Experimenting with a variety of things in parallel.
20
+
21
+ ### Bad use cases for AI
22
+
23
+ - Writing tests that don't mean anything.
24
+ - Refactoring large chunks of code.
25
+ - Assessing benchmarks.
26
+ - Writing up bug reports that you haven't experienced first hand.
27
+
28
+ ## Setup
29
+
30
+ ### Prerequisites
31
+
32
+ Rust is the only requirement for local dev, however would strongly encourage the use of `just`, `uv`, and `rustup` with `clippy`.
33
+
34
+ - [Rust toolchain](https://rustup.rs/) (with `clippy`)
35
+ - Python 3.9+
36
+ - [uv](https://github.com/astral-sh/uv) — venv + dependency management
37
+ - [just](https://github.com/casey/just) — task runner (every doc references it)
38
+
39
+ ### First-time setup
40
+
41
+ ```bash
42
+ just setup # uv venv + dev deps + maturin develop
43
+ just test # full test suite (parallel via xdist)
44
+ ```
45
+
46
+ `just setup` chains `uv venv`, `uv pip install -e ".[dev]"`, `uv lock`, and `maturin develop`. Skip `just` and you can run those steps directly.
47
+
48
+ ## Project layout
49
+
50
+ ```
51
+ src/ Rust core — pyo3 classes, transport, retry, etc.
52
+ python/rqx/ Python wrapper — re-exports + module-level functions
53
+ python/rqx/_types.pyi Type stubs for the compiled extension
54
+ tests/ pytest suite (sync + async + MTLS + streaming)
55
+ benchmarks/ Performance scripts
56
+ docs/ Project spec, report, benchmark output
57
+ ```
58
+
59
+ ## Benchmarks
60
+
61
+ Performance regressions are easy to introduce in a Rust/Python FFI project — every GIL acquire, every allocation crossing the boundary matters. Before merging anything that touches the hot path, run the relevant bench.
62
+
63
+ - `benchmarks/b1_{rqx,httpr,httpx,aiohttp}.py` + `run_b1.sh` — throughput sweep, per-client subprocess to keep tokio runtimes and the httpr threadpool from cross-contaminating. `b2_latency.py` through `b10_tls_handshake.py` cover the other dimensions (latency, pool, memory, JSON, retry overhead, network latency, concurrency sweep, payload sweep, TLS handshake).
64
+ - `benchmarks/run_all.sh` — full sweep. Builds in release mode, starts the local delay server, restarts nginx between benches to drain TCP TIME_WAIT (otherwise tail outliers explode). Output lands under `/tmp/reqx_bench_<timestamp>/`.
65
+ - `benchmarks/docker-compose.yaml` — the nginx + delay-server stack the benches hit.
66
+
67
+ **Caveat:** all numbers in `docs/bench_results/` and `docs/report.md` are from a local nginx + a local delay-server on the same machine running the client. Loopback throughput and a real remote HTTP server stress different parts of the stack (kernel TCP, DNS, TLS resumption, real RTT). Validating these results against a real internet-facing server on dedicated hardware is still TODO — tracked in [#41](https://github.com/rodcochran/rqx/issues/41). Until that runs, treat the existing numbers as relative comparisons against httpx/aiohttp on identical infrastructure, not as absolute production claims.
68
+
69
+ If you change something performance-sensitive, please include a fresh local measurement in the PR description.
70
+
71
+ ## Submitting changes
72
+
73
+ - Branch from `main`, one PR per logical change.
74
+ - PR description: short Summary, `Closes #N`, then a Testing section in prose describing what was verified. No checklist-style Test plan — say what was actually run and what it proved.
75
+ - Run `just test` locally before opening the PR. CI runs the same suite on Linux.
76
+
77
+ ## Reporting bugs
78
+
79
+ Open an issue with:
80
+
81
+ - A minimal reproducible snippet (Python code + the `rqx` version)
82
+ - What you expected to happen
83
+ - What actually happened (status codes, stack trace, whatever's relevant)
84
+ - Environment (OS, Python version)
85
+
86
+ A small repro is worth ten paragraphs of context.