httpr 0.1.17__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.
- httpr-0.1.17/.github/copilot-instructions.md +240 -0
- httpr-0.1.17/.github/workflows/CI.yml +386 -0
- httpr-0.1.17/.github/workflows/CI.yml.backup +362 -0
- httpr-0.1.17/.github/workflows/copilot-setup-steps.yml +62 -0
- httpr-0.1.17/.github/workflows/mkdocs.yml +30 -0
- httpr-0.1.17/.github/workflows/set_version.py +56 -0
- httpr-0.1.17/.gitignore +86 -0
- httpr-0.1.17/.pre-commit-config.yaml +32 -0
- httpr-0.1.17/CLAUDE.md +88 -0
- httpr-0.1.17/Cargo.lock +2180 -0
- httpr-0.1.17/Cargo.toml +55 -0
- httpr-0.1.17/LICENSE +21 -0
- httpr-0.1.17/PKG-INFO +49 -0
- httpr-0.1.17/README.md +291 -0
- httpr-0.1.17/benchmark/README.md +17 -0
- httpr-0.1.17/benchmark/benchmark.py +270 -0
- httpr-0.1.17/benchmark/generate_image.py +138 -0
- httpr-0.1.17/benchmark/pyproject.toml +23 -0
- httpr-0.1.17/benchmark/requirements.txt +11 -0
- httpr-0.1.17/benchmark/server.py +74 -0
- httpr-0.1.17/benchmark.jpg +0 -0
- httpr-0.1.17/docs/advanced/cookies.md +344 -0
- httpr-0.1.17/docs/advanced/index.md +171 -0
- httpr-0.1.17/docs/advanced/proxy.md +307 -0
- httpr-0.1.17/docs/advanced/ssl-tls.md +294 -0
- httpr-0.1.17/docs/api/async-client.md +20 -0
- httpr-0.1.17/docs/api/client.md +20 -0
- httpr-0.1.17/docs/api/functions.md +55 -0
- httpr-0.1.17/docs/api/index.md +112 -0
- httpr-0.1.17/docs/api/response.md +254 -0
- httpr-0.1.17/docs/index.md +174 -0
- httpr-0.1.17/docs/quickstart.md +288 -0
- httpr-0.1.17/docs/tutorial/async.md +357 -0
- httpr-0.1.17/docs/tutorial/authentication.md +286 -0
- httpr-0.1.17/docs/tutorial/index.md +99 -0
- httpr-0.1.17/docs/tutorial/making-requests.md +369 -0
- httpr-0.1.17/docs/tutorial/response-handling.md +308 -0
- httpr-0.1.17/docs/writings/index.md +1 -0
- httpr-0.1.17/docs/writings/posts/2025-02-24-python-http-clients-suck.md +9 -0
- httpr-0.1.17/httpr/__init__.py +870 -0
- httpr-0.1.17/httpr/httpr.pyi +101 -0
- httpr-0.1.17/httpr/py.typed +0 -0
- httpr-0.1.17/httpr.code-workspace +7 -0
- httpr-0.1.17/mkdocs.yml +119 -0
- httpr-0.1.17/pyproject.toml +48 -0
- httpr-0.1.17/scratch.ipynb +114 -0
- httpr-0.1.17/src/lib.rs +445 -0
- httpr-0.1.17/src/response.rs +199 -0
- httpr-0.1.17/src/traits.rs +87 -0
- httpr-0.1.17/src/utils.rs +250 -0
- httpr-0.1.17/tests/conftest.py +33 -0
- httpr-0.1.17/tests/httpx_conns.py +155 -0
- httpr-0.1.17/tests/test_asyncclient.py +24 -0
- httpr-0.1.17/tests/test_client.py +280 -0
- httpr-0.1.17/tests/test_defs.py +248 -0
- httpr-0.1.17/tests/test_docs.py +316 -0
- httpr-0.1.17/tests/test_ssl.py +126 -0
- httpr-0.1.17/uv.lock +3811 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# httpr - AI Coding Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
**httpr** is a high-performance HTTP client for Python built in Rust using PyO3 and reqwest. It's designed as a drop-in replacement for `httpx` and `requests` with significantly better performance through Rust's native speed.
|
|
6
|
+
|
|
7
|
+
### Directory Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
httpr/
|
|
11
|
+
├── .github/ # GitHub workflows and configurations
|
|
12
|
+
│ ├── workflows/ # CI/CD pipelines (testing, building, releasing)
|
|
13
|
+
│ └── copilot-instructions.md # This file
|
|
14
|
+
├── src/ # Rust source code
|
|
15
|
+
│ ├── lib.rs # Main RClient class, PyO3 module definition
|
|
16
|
+
│ ├── response.rs # Response object with CaseInsensitiveHeaderMap
|
|
17
|
+
│ ├── traits.rs # Python↔Rust type conversion traits
|
|
18
|
+
│ └── utils.rs # CA certificates, encoding detection
|
|
19
|
+
├── httpr/ # Python package
|
|
20
|
+
│ ├── __init__.py # Client and AsyncClient classes
|
|
21
|
+
│ ├── httpr.pyi # Type stubs for IDE support
|
|
22
|
+
│ └── py.typed # PEP 561 marker for type checking
|
|
23
|
+
├── tests/ # Python tests using pytest
|
|
24
|
+
├── benchmark/ # Performance benchmarking suite
|
|
25
|
+
├── docs/ # MkDocs documentation
|
|
26
|
+
├── Cargo.toml # Rust dependencies and build config
|
|
27
|
+
├── pyproject.toml # Python project metadata and dependencies
|
|
28
|
+
└── README.md # User-facing documentation
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Architecture
|
|
32
|
+
|
|
33
|
+
- **Rust Core** (`src/`): PyO3-based HTTP client wrapping reqwest
|
|
34
|
+
- `lib.rs`: Main `RClient` class with sync request handling via Tokio single-threaded runtime
|
|
35
|
+
- `response.rs`: `Response` object with `CaseInsensitiveHeaderMap` for HTTP headers
|
|
36
|
+
- `traits.rs`: Conversion traits between Python/Rust types (IndexMap ↔ HeaderMap)
|
|
37
|
+
- `utils.rs`: CA certificate loading, encoding detection
|
|
38
|
+
- **Python Wrapper** (`httpr/`): Thin Python layer over Rust bindings
|
|
39
|
+
- `__init__.py`: `Client` (sync) and `AsyncClient` classes with context manager support
|
|
40
|
+
- `AsyncClient` uses `asyncio.run_in_executor()` to wrap sync Rust calls - NOT native async
|
|
41
|
+
- `httpr.pyi`: Type stubs for IDE support
|
|
42
|
+
- **Build System**: Maturin (PyO3 build tool) compiles Rust → Python wheels
|
|
43
|
+
|
|
44
|
+
### Key Architectural Decisions
|
|
45
|
+
|
|
46
|
+
1. **Single Tokio Runtime**: Uses `LazyLock<Runtime>` with `new_current_thread()` - all async operations run on one thread
|
|
47
|
+
2. **Async is Sync**: `AsyncClient` runs sync Rust code in thread executor, NOT native async Rust
|
|
48
|
+
3. **Case-Insensitive Headers**: Custom `CaseInsensitiveHeaderMap` struct maintains original casing while allowing case-insensitive lookups (required for HTTP/2)
|
|
49
|
+
4. **Zero Python Dependencies**: All functionality in Rust; no runtime Python dependencies
|
|
50
|
+
|
|
51
|
+
### Key Dependencies
|
|
52
|
+
|
|
53
|
+
**Rust (Cargo.toml)**:
|
|
54
|
+
- `pyo3` (0.23.4): Python bindings, enables calling Rust from Python
|
|
55
|
+
- `reqwest` (0.12): HTTP client library, provides core networking functionality
|
|
56
|
+
- `tokio` (1.43): Async runtime for Rust (single-threaded mode)
|
|
57
|
+
- `anyhow`: Error handling with context
|
|
58
|
+
- `indexmap` + `foldhash`: Fast hash maps for headers/params
|
|
59
|
+
- `rustls-tls`: TLS implementation (no OpenSSL dependency)
|
|
60
|
+
- Compression: `gzip`, `brotli`, `zstd`, `deflate` support
|
|
61
|
+
|
|
62
|
+
**Python (pyproject.toml)**:
|
|
63
|
+
- **Zero runtime dependencies** - all functionality in Rust
|
|
64
|
+
- Dev dependencies: `pytest`, `pytest-asyncio`, `mypy`, `ruff`, `maturin`
|
|
65
|
+
- Supports Python 3.9+
|
|
66
|
+
|
|
67
|
+
## Development Workflows
|
|
68
|
+
|
|
69
|
+
### Building & Testing
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Install development dependencies (includes maturin)
|
|
73
|
+
uv sync --extra dev
|
|
74
|
+
|
|
75
|
+
# Build Rust extension in development mode (fast compilation)
|
|
76
|
+
uv run maturin develop
|
|
77
|
+
|
|
78
|
+
# Run tests (requires httpbin.org access)
|
|
79
|
+
uv run pytest tests/
|
|
80
|
+
|
|
81
|
+
# Type checking
|
|
82
|
+
uv run mypy httpr/
|
|
83
|
+
|
|
84
|
+
# Linting
|
|
85
|
+
uv run ruff check httpr/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Benchmarking
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd benchmark/
|
|
92
|
+
uv run uvicorn server:app # Terminal 1: Start test server
|
|
93
|
+
uv run python benchmark.py # Terminal 2: Run benchmarks
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The benchmark compares httpr against requests, httpx, curl_cffi, pycurl, tls_client, and aiohttp.
|
|
97
|
+
|
|
98
|
+
### Release Process
|
|
99
|
+
|
|
100
|
+
1. Tag with version: `git tag v0.1.x`
|
|
101
|
+
2. Push tag: `git push origin v0.1.x`
|
|
102
|
+
3. CI auto-builds wheels for Linux (x86_64, aarch64, armv7), macOS (Intel/ARM), Windows
|
|
103
|
+
4. Maturin publishes to PyPI
|
|
104
|
+
|
|
105
|
+
### CI/CD
|
|
106
|
+
|
|
107
|
+
**Workflows** (`.github/workflows/`):
|
|
108
|
+
- `CI.yml`: Main CI pipeline - runs tests, linting, type checking on pull requests
|
|
109
|
+
- `mkdocs.yml`: Documentation deployment to GitHub Pages
|
|
110
|
+
- Platform matrix testing: Linux (multiple architectures), macOS (Intel/ARM), Windows
|
|
111
|
+
- Automated wheel building via `maturin` for all platforms
|
|
112
|
+
- Test suite runs against live httpbin.org endpoints
|
|
113
|
+
|
|
114
|
+
### Debugging
|
|
115
|
+
|
|
116
|
+
**Rust Debugging**:
|
|
117
|
+
```bash
|
|
118
|
+
# Enable Rust logging
|
|
119
|
+
RUST_LOG=debug uv run pytest tests/test_client.py -v
|
|
120
|
+
|
|
121
|
+
# Build with debug symbols (slower but debuggable)
|
|
122
|
+
uv run maturin develop --release=false
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Python Debugging**:
|
|
126
|
+
```python
|
|
127
|
+
# Standard Python debugging works
|
|
128
|
+
import httpr
|
|
129
|
+
client = httpr.Client()
|
|
130
|
+
breakpoint() # Python debugger
|
|
131
|
+
response = client.get("https://httpbin.org/get")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Common Issues**:
|
|
135
|
+
- "Module not found" after Rust changes → Run `uv run maturin develop`
|
|
136
|
+
- SSL errors → Check `HTTPR_CA_BUNDLE` environment variable
|
|
137
|
+
- Test failures → httpbin.org may be down; tests use `@retry()` decorator
|
|
138
|
+
- Import errors → Ensure virtual environment is activated
|
|
139
|
+
|
|
140
|
+
## Project-Specific Conventions
|
|
141
|
+
|
|
142
|
+
### Python-Rust Interface Patterns
|
|
143
|
+
|
|
144
|
+
- **All Python params converted to strings**: `Client.request()` converts param values to strings before passing to Rust
|
|
145
|
+
- **Method validation in Python**: HTTP method validation happens in Python wrapper, not Rust
|
|
146
|
+
- **Optional chaining**: Client stores default `auth`, `params`, `headers` - per-request values override via `.or()`
|
|
147
|
+
|
|
148
|
+
### Type System
|
|
149
|
+
|
|
150
|
+
- Rust uses `IndexMap<String, String, RandomState>` (foldhash) for dicts
|
|
151
|
+
- Python type hints via `TypedDict` (`RequestParams`, `ClientRequestParams`)
|
|
152
|
+
- Use `Unpack` for `**kwargs` typing (supports Python 3.9+ via typing_extensions)
|
|
153
|
+
|
|
154
|
+
### Error Handling
|
|
155
|
+
|
|
156
|
+
- Rust errors bubble up via `anyhow::Result` → PyO3 exceptions
|
|
157
|
+
- No fine-grained error types yet (all raise generic `Exception`)
|
|
158
|
+
- Tests use `@retry()` decorator to handle flaky httpbin.org responses
|
|
159
|
+
|
|
160
|
+
### Testing Patterns
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Sync tests
|
|
164
|
+
def test_client_feature():
|
|
165
|
+
client = httpr.Client(auth=("user", "pass"))
|
|
166
|
+
response = client.get("https://httpbin.org/anything")
|
|
167
|
+
assert response.status_code == 200
|
|
168
|
+
|
|
169
|
+
# Async tests
|
|
170
|
+
@pytest.mark.asyncio
|
|
171
|
+
async def test_asyncclient_feature():
|
|
172
|
+
async with httpr.AsyncClient() as client:
|
|
173
|
+
response = await client.get("https://httpbin.org/anything")
|
|
174
|
+
assert response.status_code == 200
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Tests depend on httpbin.org for validation - use `@retry()` wrapper for flaky network requests.
|
|
178
|
+
|
|
179
|
+
## Critical Implementation Details
|
|
180
|
+
|
|
181
|
+
### SSL/TLS Configuration
|
|
182
|
+
|
|
183
|
+
- CA certs loaded via `HTTPR_CA_BUNDLE` env var (set before client init)
|
|
184
|
+
- `ca_cert_file` param internally sets `HTTPR_CA_BUNDLE`
|
|
185
|
+
- mTLS via `client_pem` parameter (PEM format identity file)
|
|
186
|
+
- `verify=False` enables `danger_accept_invalid_certs()`
|
|
187
|
+
|
|
188
|
+
### Headers Behavior
|
|
189
|
+
|
|
190
|
+
- Headers are **lowercased** internally (HTTP/2 requirement)
|
|
191
|
+
- `client.headers` getter excludes `Cookie` header
|
|
192
|
+
- `client.cookies` getter/setter extracts from `Cookie` header
|
|
193
|
+
- Use `CaseInsensitiveHeaderMap` in Rust for lookups
|
|
194
|
+
|
|
195
|
+
### Request Body Handling
|
|
196
|
+
|
|
197
|
+
- Mutually exclusive: `content` (bytes), `data` (form), `json` (JSON), `files` (multipart)
|
|
198
|
+
- `data` and `json` use `pythonize::depythonize()` for Python → Rust serde_json::Value
|
|
199
|
+
- `files` dict maps field names to file paths (auto-reads and sends as multipart)
|
|
200
|
+
|
|
201
|
+
### Proxy Support
|
|
202
|
+
|
|
203
|
+
- Set via `proxy` param or `HTTPR_PROXY` env var
|
|
204
|
+
- Changing `client.proxy` rebuilds entire reqwest client (expensive operation)
|
|
205
|
+
|
|
206
|
+
## Common Tasks
|
|
207
|
+
|
|
208
|
+
### Adding New Request Parameters
|
|
209
|
+
|
|
210
|
+
1. Add to `RequestParams` TypedDict in `httpr.pyi`
|
|
211
|
+
2. Add parameter to `RClient.request()` signature in `src/lib.rs`
|
|
212
|
+
3. Handle parameter in request builder chain
|
|
213
|
+
4. Update docstrings in both Python and Rust
|
|
214
|
+
|
|
215
|
+
### Modifying Response Properties
|
|
216
|
+
|
|
217
|
+
1. Add Rust implementation in `src/response.rs`
|
|
218
|
+
2. Add `#[pymethods]` getter/method
|
|
219
|
+
3. Add type hint to `Response` class in `httpr.pyi`
|
|
220
|
+
|
|
221
|
+
### Performance Optimization
|
|
222
|
+
|
|
223
|
+
- Check `Cargo.toml` for reqwest feature flags (enable compression codecs, http2, etc.)
|
|
224
|
+
- Profile with `cargo flamegraph` on Rust side
|
|
225
|
+
- Benchmark against other clients using `benchmark/benchmark.py`
|
|
226
|
+
|
|
227
|
+
## What NOT to Do
|
|
228
|
+
|
|
229
|
+
- Don't add Python dependencies (defeats "zero dependencies" goal)
|
|
230
|
+
- Don't use native async Rust in request path (breaks single-threaded runtime model)
|
|
231
|
+
- Don't modify headers case-sensitivity behavior (HTTP/2 spec requirement)
|
|
232
|
+
- Don't remove reqwest compression features (users expect automatic gzip/brotli)
|
|
233
|
+
- Don't skip `maturin develop` after Rust changes (Python won't see updates)
|
|
234
|
+
|
|
235
|
+
## Resources
|
|
236
|
+
|
|
237
|
+
- [PyO3 User Guide](https://pyo3.rs/)
|
|
238
|
+
- [reqwest docs](https://docs.rs/reqwest/)
|
|
239
|
+
- [Maturin Guide](https://www.maturin.rs/)
|
|
240
|
+
- Benchmark results: `benchmark.jpg` in repo root
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "*"
|
|
7
|
+
pull_request:
|
|
8
|
+
schedule:
|
|
9
|
+
# Run weekly on Sundays at midnight UTC
|
|
10
|
+
- cron: "0 0 * * 0"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
# Fast test job - runs on PRs with Python version matrix
|
|
18
|
+
test:
|
|
19
|
+
runs-on: ubuntu-22.04
|
|
20
|
+
if: github.event_name == 'pull_request'
|
|
21
|
+
strategy:
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: astral-sh/setup-uv@v7
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python-version }}
|
|
29
|
+
enable-cache: false
|
|
30
|
+
- name: Build and test
|
|
31
|
+
run: |
|
|
32
|
+
uv sync --extra dev
|
|
33
|
+
uv run maturin develop
|
|
34
|
+
uv run pytest
|
|
35
|
+
|
|
36
|
+
# Docs build test - runs on PRs to catch broken docs early
|
|
37
|
+
docs:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
if: github.event_name == 'pull_request'
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: astral-sh/setup-uv@v7
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.12"
|
|
45
|
+
enable-cache: false
|
|
46
|
+
- name: Build docs
|
|
47
|
+
run: |
|
|
48
|
+
uv sync --extra docs
|
|
49
|
+
uv run mkdocs build --strict
|
|
50
|
+
|
|
51
|
+
# Build jobs - run on tags, weekly schedule, or manual dispatch
|
|
52
|
+
linux:
|
|
53
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
54
|
+
if: github.event_name != 'pull_request'
|
|
55
|
+
strategy:
|
|
56
|
+
matrix:
|
|
57
|
+
platform:
|
|
58
|
+
- runner: ubuntu-22.04
|
|
59
|
+
target: x86_64
|
|
60
|
+
- runner: ubuntu-22.04
|
|
61
|
+
target: x86
|
|
62
|
+
- runner: ubuntu-24.04-arm
|
|
63
|
+
target: aarch64
|
|
64
|
+
- runner: ubuntu-22.04
|
|
65
|
+
target: armv7
|
|
66
|
+
# - runner: ubuntu-22.04
|
|
67
|
+
# target: s390x
|
|
68
|
+
# - runner: ubuntu-22.04
|
|
69
|
+
# target: ppc64le
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- uses: astral-sh/setup-uv@v7
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.12"
|
|
75
|
+
enable-cache: false
|
|
76
|
+
# Only modify pyproject.toml temporarily for this job if running from a tag
|
|
77
|
+
- name: Update version in pyproject.toml
|
|
78
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
79
|
+
run: |
|
|
80
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
81
|
+
uvx --with toml python .github/workflows/set_version.py $TAG_VERSION
|
|
82
|
+
- name: Build wheels
|
|
83
|
+
uses: PyO3/maturin-action@v1
|
|
84
|
+
with:
|
|
85
|
+
target: ${{ matrix.platform.target }}
|
|
86
|
+
args: --release --out dist
|
|
87
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
88
|
+
manylinux: auto
|
|
89
|
+
- name: Build free-threaded wheels
|
|
90
|
+
uses: PyO3/maturin-action@v1
|
|
91
|
+
with:
|
|
92
|
+
target: ${{ matrix.platform.target }}
|
|
93
|
+
args: --release --out dist -i python3.13t
|
|
94
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
95
|
+
manylinux: auto
|
|
96
|
+
- name: Upload wheels
|
|
97
|
+
uses: actions/upload-artifact@v4
|
|
98
|
+
with:
|
|
99
|
+
name: wheels-linux-${{ matrix.platform.target }}
|
|
100
|
+
path: dist
|
|
101
|
+
- name: pytest
|
|
102
|
+
if: ${{ matrix.platform.target == 'x86_64' || matrix.platform.target == 'aarch64' }}
|
|
103
|
+
shell: bash
|
|
104
|
+
env:
|
|
105
|
+
RUSTC_WRAPPER: ""
|
|
106
|
+
run: |
|
|
107
|
+
set -e
|
|
108
|
+
uv sync --extra dev --find-links dist --reinstall
|
|
109
|
+
uv run pytest
|
|
110
|
+
- name: pytest
|
|
111
|
+
if: ${{ matrix.platform.target == 'armv7' }}
|
|
112
|
+
uses: uraimo/run-on-arch-action@v2
|
|
113
|
+
with:
|
|
114
|
+
arch: ${{ matrix.platform.target }}
|
|
115
|
+
distro: ubuntu22.04
|
|
116
|
+
githubToken: ${{ github.token }}
|
|
117
|
+
install: |
|
|
118
|
+
apt-get update
|
|
119
|
+
apt-get install -y --no-install-recommends python3 python3-pip python3-dev build-essential libffi-dev
|
|
120
|
+
pip3 install -U pip
|
|
121
|
+
run: |
|
|
122
|
+
set -e
|
|
123
|
+
pip3 install httpr --find-links dist --no-index --force-reinstall
|
|
124
|
+
pip3 install certifi pytest pytest-asyncio pytest-httpbin mypy ruff maturin trustme
|
|
125
|
+
pytest
|
|
126
|
+
|
|
127
|
+
musllinux:
|
|
128
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
129
|
+
if: github.event_name != 'pull_request'
|
|
130
|
+
strategy:
|
|
131
|
+
matrix:
|
|
132
|
+
platform:
|
|
133
|
+
- runner: ubuntu-22.04
|
|
134
|
+
target: x86_64
|
|
135
|
+
- runner: ubuntu-22.04
|
|
136
|
+
target: x86
|
|
137
|
+
- runner: ubuntu-24.04-arm
|
|
138
|
+
target: aarch64
|
|
139
|
+
# - runner: ubuntu-22.04
|
|
140
|
+
# target: armv7
|
|
141
|
+
steps:
|
|
142
|
+
- uses: actions/checkout@v4
|
|
143
|
+
- uses: astral-sh/setup-uv@v7
|
|
144
|
+
with:
|
|
145
|
+
python-version: "3.12"
|
|
146
|
+
enable-cache: false
|
|
147
|
+
# Only modify pyproject.toml temporarily for this job if running from a tag
|
|
148
|
+
- name: Update version in pyproject.toml
|
|
149
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
150
|
+
run: |
|
|
151
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
152
|
+
uvx --with toml python .github/workflows/set_version.py $TAG_VERSION
|
|
153
|
+
- name: Build wheels
|
|
154
|
+
uses: PyO3/maturin-action@v1
|
|
155
|
+
with:
|
|
156
|
+
target: ${{ matrix.platform.target }}
|
|
157
|
+
args: --release --out dist
|
|
158
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
159
|
+
manylinux: musllinux_1_2
|
|
160
|
+
- name: Build free-threaded wheels
|
|
161
|
+
uses: PyO3/maturin-action@v1
|
|
162
|
+
with:
|
|
163
|
+
target: ${{ matrix.platform.target }}
|
|
164
|
+
args: --release --out dist -i python3.13t
|
|
165
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
166
|
+
manylinux: musllinux_1_2
|
|
167
|
+
- name: Upload wheels
|
|
168
|
+
uses: actions/upload-artifact@v4
|
|
169
|
+
with:
|
|
170
|
+
name: wheels-musllinux-${{ matrix.platform.target }}
|
|
171
|
+
path: dist
|
|
172
|
+
- name: pytest
|
|
173
|
+
if: ${{ matrix.platform.target == 'x86_64' || matrix.platform.target == 'aarch64' }}
|
|
174
|
+
uses: addnab/docker-run-action@v3
|
|
175
|
+
with:
|
|
176
|
+
image: alpine:latest
|
|
177
|
+
options: -v ${{ github.workspace }}:/io -w /io
|
|
178
|
+
run: |
|
|
179
|
+
set -e
|
|
180
|
+
apk add py3-pip py3-virtualenv python3-dev build-base libffi-dev
|
|
181
|
+
python3 -m virtualenv .venv
|
|
182
|
+
source .venv/bin/activate
|
|
183
|
+
pip install httpr --find-links dist --no-index --force-reinstall
|
|
184
|
+
pip install certifi pytest pytest-asyncio pytest-httpbin mypy ruff maturin trustme
|
|
185
|
+
pytest
|
|
186
|
+
|
|
187
|
+
windows:
|
|
188
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
189
|
+
if: github.event_name != 'pull_request'
|
|
190
|
+
strategy:
|
|
191
|
+
matrix:
|
|
192
|
+
platform:
|
|
193
|
+
- runner: windows-latest
|
|
194
|
+
target: x64
|
|
195
|
+
- runner: windows-latest
|
|
196
|
+
target: x86
|
|
197
|
+
steps:
|
|
198
|
+
- uses: actions/checkout@v4
|
|
199
|
+
- uses: actions/setup-python@v5
|
|
200
|
+
with:
|
|
201
|
+
python-version: "3.12"
|
|
202
|
+
architecture: ${{ matrix.platform.target }}
|
|
203
|
+
- uses: astral-sh/setup-uv@v7
|
|
204
|
+
with:
|
|
205
|
+
python-version: "3.12"
|
|
206
|
+
enable-cache: false
|
|
207
|
+
# Only modify pyproject.toml temporarily for this job if running from a tag
|
|
208
|
+
- name: Update version in pyproject.toml
|
|
209
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
210
|
+
shell: pwsh
|
|
211
|
+
run: |
|
|
212
|
+
$TAG_VERSION = $env:GITHUB_REF -replace 'refs/tags/v', ''
|
|
213
|
+
uvx --with toml python .github/workflows/set_version.py $TAG_VERSION
|
|
214
|
+
- name: Build wheels
|
|
215
|
+
uses: PyO3/maturin-action@v1
|
|
216
|
+
with:
|
|
217
|
+
target: ${{ matrix.platform.target }}
|
|
218
|
+
args: --release --out dist -i python3.12
|
|
219
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
220
|
+
- uses: actions/setup-python@v5
|
|
221
|
+
with:
|
|
222
|
+
python-version: 3.13t
|
|
223
|
+
architecture: ${{ matrix.platform.target }}
|
|
224
|
+
- name: Build free-threaded wheels
|
|
225
|
+
uses: PyO3/maturin-action@v1
|
|
226
|
+
with:
|
|
227
|
+
target: ${{ matrix.platform.target }}
|
|
228
|
+
args: --release --out dist -i python3.13t
|
|
229
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
230
|
+
- name: Upload wheels
|
|
231
|
+
uses: actions/upload-artifact@v4
|
|
232
|
+
with:
|
|
233
|
+
name: wheels-windows-${{ matrix.platform.target }}
|
|
234
|
+
path: dist
|
|
235
|
+
- name: pytest
|
|
236
|
+
shell: bash
|
|
237
|
+
env:
|
|
238
|
+
RUSTC_WRAPPER: ""
|
|
239
|
+
run: |
|
|
240
|
+
set -e
|
|
241
|
+
uv sync --extra dev --find-links dist --reinstall
|
|
242
|
+
uv run pytest
|
|
243
|
+
|
|
244
|
+
macos:
|
|
245
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
246
|
+
if: github.event_name != 'pull_request'
|
|
247
|
+
strategy:
|
|
248
|
+
matrix:
|
|
249
|
+
platform:
|
|
250
|
+
- runner: macos-15
|
|
251
|
+
target: x86_64
|
|
252
|
+
- runner: macos-14
|
|
253
|
+
target: aarch64
|
|
254
|
+
steps:
|
|
255
|
+
- uses: actions/checkout@v4
|
|
256
|
+
- uses: astral-sh/setup-uv@v7
|
|
257
|
+
with:
|
|
258
|
+
python-version: "3.12"
|
|
259
|
+
enable-cache: false
|
|
260
|
+
# Only modify pyproject.toml temporarily for this job if running from a tag
|
|
261
|
+
- name: Update version in pyproject.toml
|
|
262
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
263
|
+
run: |
|
|
264
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
265
|
+
uvx --with toml python .github/workflows/set_version.py $TAG_VERSION
|
|
266
|
+
- name: Build wheels
|
|
267
|
+
uses: PyO3/maturin-action@v1
|
|
268
|
+
with:
|
|
269
|
+
target: ${{ matrix.platform.target }}
|
|
270
|
+
args: --release --out dist
|
|
271
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
272
|
+
- name: Build free-threaded wheels
|
|
273
|
+
uses: PyO3/maturin-action@v1
|
|
274
|
+
with:
|
|
275
|
+
target: ${{ matrix.platform.target }}
|
|
276
|
+
args: --release --out dist -i python3.13t
|
|
277
|
+
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
278
|
+
- name: Upload wheels
|
|
279
|
+
uses: actions/upload-artifact@v4
|
|
280
|
+
with:
|
|
281
|
+
name: wheels-macos-${{ matrix.platform.target }}
|
|
282
|
+
path: dist
|
|
283
|
+
- name: pytest
|
|
284
|
+
env:
|
|
285
|
+
RUSTC_WRAPPER: ""
|
|
286
|
+
run: |
|
|
287
|
+
set -e
|
|
288
|
+
uv sync --extra dev --find-links dist --reinstall
|
|
289
|
+
uv run pytest
|
|
290
|
+
|
|
291
|
+
sdist:
|
|
292
|
+
runs-on: ubuntu-latest
|
|
293
|
+
if: github.event_name != 'pull_request'
|
|
294
|
+
steps:
|
|
295
|
+
- uses: actions/checkout@v4
|
|
296
|
+
- uses: astral-sh/setup-uv@v7
|
|
297
|
+
with:
|
|
298
|
+
python-version: "3.12"
|
|
299
|
+
enable-cache: false
|
|
300
|
+
# Only modify pyproject.toml temporarily for this job if running from a tag
|
|
301
|
+
- name: Update version in pyproject.toml
|
|
302
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
303
|
+
run: |
|
|
304
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
305
|
+
uvx --with toml python .github/workflows/set_version.py $TAG_VERSION
|
|
306
|
+
- name: Build sdist
|
|
307
|
+
uses: PyO3/maturin-action@v1
|
|
308
|
+
with:
|
|
309
|
+
command: sdist
|
|
310
|
+
args: --out dist
|
|
311
|
+
- name: Upload sdist
|
|
312
|
+
uses: actions/upload-artifact@v4
|
|
313
|
+
with:
|
|
314
|
+
name: wheels-sdist
|
|
315
|
+
path: dist
|
|
316
|
+
|
|
317
|
+
release:
|
|
318
|
+
name: Release
|
|
319
|
+
environment: pypi
|
|
320
|
+
runs-on: ubuntu-latest
|
|
321
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
|
|
322
|
+
needs: [linux, musllinux, windows, macos, sdist]
|
|
323
|
+
permissions:
|
|
324
|
+
# Use to sign the release artifacts
|
|
325
|
+
id-token: write
|
|
326
|
+
# Used to upload release artifacts
|
|
327
|
+
contents: write
|
|
328
|
+
# Used to generate artifact attestation
|
|
329
|
+
attestations: write
|
|
330
|
+
steps:
|
|
331
|
+
- uses: actions/download-artifact@v4
|
|
332
|
+
- name: Generate artifact attestation
|
|
333
|
+
uses: actions/attest-build-provenance@v2
|
|
334
|
+
with:
|
|
335
|
+
subject-path: "wheels-*/*"
|
|
336
|
+
- name: Publish to PyPI
|
|
337
|
+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
338
|
+
uses: PyO3/maturin-action@v1
|
|
339
|
+
env:
|
|
340
|
+
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
341
|
+
with:
|
|
342
|
+
command: upload
|
|
343
|
+
args: --non-interactive --skip-existing wheels-*/*
|
|
344
|
+
|
|
345
|
+
benchmark:
|
|
346
|
+
permissions:
|
|
347
|
+
contents: write
|
|
348
|
+
runs-on: ubuntu-latest
|
|
349
|
+
needs: [linux]
|
|
350
|
+
steps:
|
|
351
|
+
- uses: actions/checkout@v4
|
|
352
|
+
with:
|
|
353
|
+
fetch-depth: 0
|
|
354
|
+
- uses: astral-sh/setup-uv@v7
|
|
355
|
+
with:
|
|
356
|
+
python-version: "3.12"
|
|
357
|
+
enable-cache: false
|
|
358
|
+
- name: Download wheels
|
|
359
|
+
uses: actions/download-artifact@v4
|
|
360
|
+
with:
|
|
361
|
+
name: wheels-linux-x86_64
|
|
362
|
+
- name: Install dependencies
|
|
363
|
+
run: uv sync --package benchmark --find-links ./ --reinstall
|
|
364
|
+
- name: Start Uvicorn server
|
|
365
|
+
run: |
|
|
366
|
+
uv run --package benchmark uvicorn benchmark.server:app --host 0.0.0.0 --port 8000 &
|
|
367
|
+
sleep 10
|
|
368
|
+
- name: Run benchmark
|
|
369
|
+
run: uv run --package benchmark python benchmark/benchmark.py
|
|
370
|
+
- name: Generate and commit benchmark image
|
|
371
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule'
|
|
372
|
+
run: |
|
|
373
|
+
uv run --package benchmark python benchmark/generate_image.py
|
|
374
|
+
git config --global user.name 'github-actions[bot]'
|
|
375
|
+
git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
|
376
|
+
git add *.jpg
|
|
377
|
+
if ! git diff --quiet --cached; then
|
|
378
|
+
if [ "${{ github.event_name }}" = "schedule" ]; then
|
|
379
|
+
git commit -m "Update benchmark image (weekly)"
|
|
380
|
+
else
|
|
381
|
+
git commit -m "Update benchmark image for ${{ github.ref_name }}"
|
|
382
|
+
fi
|
|
383
|
+
git push
|
|
384
|
+
else
|
|
385
|
+
echo "No changes to commit"
|
|
386
|
+
fi
|