lkrequest 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.
- lkrequest-0.1.0/.editorconfig +23 -0
- lkrequest-0.1.0/.github/workflows/ci.yml +195 -0
- lkrequest-0.1.0/.gitignore +20 -0
- lkrequest-0.1.0/CHANGELOG.md +41 -0
- lkrequest-0.1.0/CONTRIBUTING.md +74 -0
- lkrequest-0.1.0/Cargo.lock +3214 -0
- lkrequest-0.1.0/Cargo.toml +64 -0
- lkrequest-0.1.0/LICENSE +201 -0
- lkrequest-0.1.0/NOTICE +9 -0
- lkrequest-0.1.0/PKG-INFO +900 -0
- lkrequest-0.1.0/README-zh.md +870 -0
- lkrequest-0.1.0/README.md +871 -0
- lkrequest-0.1.0/SECURITY.md +28 -0
- lkrequest-0.1.0/benchmarks/README.md +49 -0
- lkrequest-0.1.0/benchmarks/bench_latency.py +95 -0
- lkrequest-0.1.0/benchmarks/bench_memory.py +75 -0
- lkrequest-0.1.0/benchmarks/bench_throughput.py +126 -0
- lkrequest-0.1.0/benchmarks/requirements.txt +5 -0
- lkrequest-0.1.0/build.rs +7 -0
- lkrequest-0.1.0/examples/accept_encoding.py +70 -0
- lkrequest-0.1.0/examples/authentication.py +44 -0
- lkrequest-0.1.0/examples/basic_requests.py +101 -0
- lkrequest-0.1.0/examples/browser_fingerprints.py +88 -0
- lkrequest-0.1.0/examples/certificate_config.py +59 -0
- lkrequest-0.1.0/examples/client_pool.py +77 -0
- lkrequest-0.1.0/examples/connection_prewarming.py +91 -0
- lkrequest-0.1.0/examples/cookie_management.py +80 -0
- lkrequest-0.1.0/examples/custom_fingerprint.py +268 -0
- lkrequest-0.1.0/examples/error_handling.py +98 -0
- lkrequest-0.1.0/examples/event_hooks.py +83 -0
- lkrequest-0.1.0/examples/fingerprint_validation.py +68 -0
- lkrequest-0.1.0/examples/http_methods.py +44 -0
- lkrequest-0.1.0/examples/logging_config.py +58 -0
- lkrequest-0.1.0/examples/metrics.py +55 -0
- lkrequest-0.1.0/examples/middleware.py +108 -0
- lkrequest-0.1.0/examples/multipart_upload.py +59 -0
- lkrequest-0.1.0/examples/pool_stats.py +60 -0
- lkrequest-0.1.0/examples/proxy_pool.py +124 -0
- lkrequest-0.1.0/examples/redirect_history.py +72 -0
- lkrequest-0.1.0/examples/response_inspection.py +106 -0
- lkrequest-0.1.0/examples/retry_strategies.py +98 -0
- lkrequest-0.1.0/examples/session_pool.py +94 -0
- lkrequest-0.1.0/examples/streaming_response.py +87 -0
- lkrequest-0.1.0/examples/timeout_config.py +72 -0
- lkrequest-0.1.0/examples/websocket.py +85 -0
- lkrequest-0.1.0/justfile +58 -0
- lkrequest-0.1.0/pyproject.toml +67 -0
- lkrequest-0.1.0/python/lkrequest/__init__.py +215 -0
- lkrequest-0.1.0/python/lkrequest/_lkrequest.pyi +1923 -0
- lkrequest-0.1.0/python/lkrequest/blocking.py +79 -0
- lkrequest-0.1.0/python/lkrequest/pcap.py +161 -0
- lkrequest-0.1.0/rustfmt.toml +3 -0
- lkrequest-0.1.0/src/client.rs +1289 -0
- lkrequest-0.1.0/src/client_pool.rs +113 -0
- lkrequest-0.1.0/src/error.rs +48 -0
- lkrequest-0.1.0/src/fingerprint.rs +1207 -0
- lkrequest-0.1.0/src/hsts.rs +121 -0
- lkrequest-0.1.0/src/lib.rs +298 -0
- lkrequest-0.1.0/src/metrics.rs +263 -0
- lkrequest-0.1.0/src/middleware.rs +188 -0
- lkrequest-0.1.0/src/multipart.rs +70 -0
- lkrequest-0.1.0/src/priority.rs +91 -0
- lkrequest-0.1.0/src/protocol.rs +200 -0
- lkrequest-0.1.0/src/proxy.rs +267 -0
- lkrequest-0.1.0/src/quic.rs +85 -0
- lkrequest-0.1.0/src/randomize.rs +246 -0
- lkrequest-0.1.0/src/response.rs +734 -0
- lkrequest-0.1.0/src/retry.rs +113 -0
- lkrequest-0.1.0/src/session.rs +1865 -0
- lkrequest-0.1.0/src/session_pool.rs +332 -0
- lkrequest-0.1.0/src/types.rs +438 -0
- lkrequest-0.1.0/src/websocket.rs +318 -0
- lkrequest-0.1.0/tests/conftest.py +187 -0
- lkrequest-0.1.0/tests/test_advanced.py +1589 -0
- lkrequest-0.1.0/tests/test_client.py +858 -0
- lkrequest-0.1.0/tests/test_followup_features.py +382 -0
- lkrequest-0.1.0/tests/test_local_server.py +120 -0
- lkrequest-0.1.0/tests/test_package_metadata.py +9 -0
- lkrequest-0.1.0/tests/test_stub_parity.py +125 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
end_of_line = lf
|
|
5
|
+
insert_final_newline = true
|
|
6
|
+
trim_trailing_whitespace = true
|
|
7
|
+
charset = utf-8
|
|
8
|
+
|
|
9
|
+
[*.rs]
|
|
10
|
+
indent_style = space
|
|
11
|
+
indent_size = 4
|
|
12
|
+
|
|
13
|
+
[*.py]
|
|
14
|
+
indent_style = space
|
|
15
|
+
indent_size = 4
|
|
16
|
+
|
|
17
|
+
[*.{toml,yml,yaml}]
|
|
18
|
+
indent_style = space
|
|
19
|
+
indent_size = 2
|
|
20
|
+
|
|
21
|
+
[Justfile]
|
|
22
|
+
indent_style = space
|
|
23
|
+
indent_size = 4
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main, master]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
env:
|
|
14
|
+
CARGO_TERM_COLOR: always
|
|
15
|
+
# Keep PEP 517 source installs aligned with the all-feature release wheels.
|
|
16
|
+
MATURIN_PEP517_ARGS: "--all-features"
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
lint:
|
|
20
|
+
name: Lint
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
25
|
+
with:
|
|
26
|
+
components: rustfmt, clippy
|
|
27
|
+
- name: cargo fmt --check
|
|
28
|
+
run: cargo fmt --check
|
|
29
|
+
- name: cargo clippy
|
|
30
|
+
run: cargo clippy --all-targets --all-features -- -D warnings
|
|
31
|
+
|
|
32
|
+
test:
|
|
33
|
+
name: Test (source)
|
|
34
|
+
needs: [lint]
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
- name: Build & install (source, all features) + test deps
|
|
43
|
+
run: pip install ".[test]"
|
|
44
|
+
- name: Verify all feature APIs
|
|
45
|
+
run: python -c "import lkrequest; required = ('QuicProfile', 'Layers', 'NegotiabilityFloor'); missing = [name for name in required if not hasattr(lkrequest, name)]; assert not missing, f'missing all-feature APIs:{missing}'"
|
|
46
|
+
- name: Run tests
|
|
47
|
+
run: pytest tests/ -v
|
|
48
|
+
|
|
49
|
+
build-wheels:
|
|
50
|
+
name: Wheel · ${{ matrix.target }}
|
|
51
|
+
needs: [test]
|
|
52
|
+
runs-on: ${{ matrix.runner }}
|
|
53
|
+
strategy:
|
|
54
|
+
fail-fast: false
|
|
55
|
+
matrix:
|
|
56
|
+
include:
|
|
57
|
+
- target: x86_64-unknown-linux-gnu
|
|
58
|
+
runner: ubuntu-latest
|
|
59
|
+
manylinux: "2_28"
|
|
60
|
+
- target: x86_64-pc-windows-msvc
|
|
61
|
+
runner: windows-latest
|
|
62
|
+
manylinux: "auto"
|
|
63
|
+
- target: aarch64-apple-darwin
|
|
64
|
+
runner: macos-15
|
|
65
|
+
manylinux: "auto"
|
|
66
|
+
- target: x86_64-apple-darwin
|
|
67
|
+
runner: macos-15-intel
|
|
68
|
+
manylinux: "auto"
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
- name: Set up NASM (Windows)
|
|
72
|
+
if: runner.os == 'Windows'
|
|
73
|
+
uses: ilammy/setup-nasm@v1
|
|
74
|
+
- name: Build wheel
|
|
75
|
+
uses: PyO3/maturin-action@v1
|
|
76
|
+
with:
|
|
77
|
+
target: ${{ matrix.target }}
|
|
78
|
+
manylinux: ${{ matrix.manylinux }}
|
|
79
|
+
args: --release --strip --all-features --out dist
|
|
80
|
+
sccache: "true"
|
|
81
|
+
before-script-linux: |
|
|
82
|
+
dnf install -y cmake perl clang || yum install -y cmake perl clang
|
|
83
|
+
- name: Upload wheel artifact
|
|
84
|
+
uses: actions/upload-artifact@v4
|
|
85
|
+
with:
|
|
86
|
+
name: wheel-${{ matrix.target }}
|
|
87
|
+
path: dist/*.whl
|
|
88
|
+
if-no-files-found: error
|
|
89
|
+
|
|
90
|
+
build-sdist:
|
|
91
|
+
name: sdist
|
|
92
|
+
needs: [test]
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
- name: Build sdist
|
|
97
|
+
uses: PyO3/maturin-action@v1
|
|
98
|
+
with:
|
|
99
|
+
command: sdist
|
|
100
|
+
args: --out dist
|
|
101
|
+
- name: Upload sdist artifact
|
|
102
|
+
uses: actions/upload-artifact@v4
|
|
103
|
+
with:
|
|
104
|
+
name: sdist
|
|
105
|
+
path: dist/*.tar.gz
|
|
106
|
+
if-no-files-found: error
|
|
107
|
+
|
|
108
|
+
test-wheel:
|
|
109
|
+
name: Test wheel · ${{ matrix.os }} · py${{ matrix.python }}
|
|
110
|
+
needs: [build-wheels]
|
|
111
|
+
runs-on: ${{ matrix.os }}
|
|
112
|
+
strategy:
|
|
113
|
+
fail-fast: false
|
|
114
|
+
matrix:
|
|
115
|
+
include:
|
|
116
|
+
- os: ubuntu-latest
|
|
117
|
+
wheel: wheel-x86_64-unknown-linux-gnu
|
|
118
|
+
python: "3.9"
|
|
119
|
+
- os: ubuntu-latest
|
|
120
|
+
wheel: wheel-x86_64-unknown-linux-gnu
|
|
121
|
+
python: "3.12"
|
|
122
|
+
- os: windows-latest
|
|
123
|
+
wheel: wheel-x86_64-pc-windows-msvc
|
|
124
|
+
python: "3.12"
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: actions/setup-python@v5
|
|
128
|
+
with:
|
|
129
|
+
python-version: ${{ matrix.python }}
|
|
130
|
+
- uses: actions/download-artifact@v4
|
|
131
|
+
with:
|
|
132
|
+
name: ${{ matrix.wheel }}
|
|
133
|
+
path: dist
|
|
134
|
+
- name: Install wheel + test deps
|
|
135
|
+
shell: bash
|
|
136
|
+
run: pip install "$(ls dist/*.whl)[test]"
|
|
137
|
+
- name: Verify all feature APIs
|
|
138
|
+
run: python -c "import lkrequest; required = ('QuicProfile', 'Layers', 'NegotiabilityFloor'); missing = [name for name in required if not hasattr(lkrequest, name)]; assert not missing, f'missing all-feature APIs:{missing}'"
|
|
139
|
+
- name: Run tests
|
|
140
|
+
run: pytest tests/ -v
|
|
141
|
+
|
|
142
|
+
release-assets:
|
|
143
|
+
name: Upload to GitHub Release
|
|
144
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
145
|
+
needs: [test-wheel, build-sdist]
|
|
146
|
+
runs-on: ubuntu-latest
|
|
147
|
+
permissions:
|
|
148
|
+
contents: write
|
|
149
|
+
steps:
|
|
150
|
+
- uses: actions/download-artifact@v4
|
|
151
|
+
with:
|
|
152
|
+
path: dist
|
|
153
|
+
merge-multiple: true
|
|
154
|
+
- name: Attach artifacts to release
|
|
155
|
+
uses: softprops/action-gh-release@v2
|
|
156
|
+
with:
|
|
157
|
+
files: |
|
|
158
|
+
dist/*.whl
|
|
159
|
+
dist/*.tar.gz
|
|
160
|
+
fail_on_unmatched_files: true
|
|
161
|
+
|
|
162
|
+
check-pypi-token:
|
|
163
|
+
name: Check PyPI token
|
|
164
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
outputs:
|
|
167
|
+
configured: ${{ steps.check.outputs.configured }}
|
|
168
|
+
steps:
|
|
169
|
+
- id: check
|
|
170
|
+
env:
|
|
171
|
+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
172
|
+
run: |
|
|
173
|
+
if [ -n "$PYPI_API_TOKEN" ]; then
|
|
174
|
+
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
175
|
+
echo "PYPI_API_TOKEN is set; publish-pypi will run."
|
|
176
|
+
else
|
|
177
|
+
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
178
|
+
echo "::notice::PYPI_API_TOKEN is not set; skipping publish-pypi (GitHub Release assets are unaffected)."
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
publish-pypi:
|
|
182
|
+
name: Publish to PyPI
|
|
183
|
+
if: startsWith(github.ref, 'refs/tags/v') && needs.check-pypi-token.outputs.configured == 'true'
|
|
184
|
+
needs: [test-wheel, build-sdist, check-pypi-token]
|
|
185
|
+
runs-on: ubuntu-latest
|
|
186
|
+
steps:
|
|
187
|
+
- uses: actions/download-artifact@v4
|
|
188
|
+
with:
|
|
189
|
+
path: dist
|
|
190
|
+
merge-multiple: true
|
|
191
|
+
- name: Publish
|
|
192
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
193
|
+
with:
|
|
194
|
+
packages-dir: dist
|
|
195
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All important changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## lkrequest-py 0.2.0
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
- **Synthetic Fingerprint Negotiability (`synthetic-fp`)** — Added `NegotiabilityFloor.UNIVERSAL`, `NegotiabilityFloor.PRESET_FAMILY`, `NegotiabilityFloor.custom(...)`, and `Randomize.negotiability(...)` so synthesized ClientHello profiles retain a configurable signature-algorithm compatibility floor.
|
|
9
|
+
- **Multi-hop Proxy Chains** — Added `ProxyConfig.parse_chain(...)` and `.through(...)`, with chain metadata via `hop_count()` and `identity()`. Client sessions, proxy pools, and session pools now accept either proxy URLs or `ProxyConfig` objects.
|
|
10
|
+
- **Mixed Proxy Transports** — HTTP/1.1 and HTTP/2 can traverse ordered combinations of HTTP CONNECT, SOCKS5, and SOCKS5H hops. QUIC/HTTP3 supports all-SOCKS5 chains when the optional `quic-h3` feature is enabled.
|
|
11
|
+
- **Chrome 150 QUIC/H3 (`quic-h3`)** — `Client.chrome_150()` now uses dedicated Chrome 150 QUIC TLS and HTTP/3 profiles instead of reusing Chrome 146. The preset covers captured signature algorithms, QUIC transport parameters, QPACK limits, navigation `PRIORITY_UPDATE`, connection-ID lengths, and Initial datagram sizing. Added `QuicProfile.chrome_150()` and the `"chrome_150"` QUIC profile name.
|
|
12
|
+
|
|
13
|
+
### 🐞 Fixes
|
|
14
|
+
- **TLS Handshake Reassembly** — TLS 1.2 and TLS 1.3 handshake messages fragmented across multiple records are now buffered and reassembled correctly, preventing failures such as `truncated handshake message in record` without changing ClientHello or fingerprint output.
|
|
15
|
+
- **SOCKS5 UDP Relay Compatibility** — QUIC-over-SOCKS5 accepts relay replies from the advertised source IP even when the relay uses a different egress port, preventing affected HTTP/3 requests from hanging.
|
|
16
|
+
- **Package Version Reporting** — `lkrequest.__version__` is now read from installed distribution metadata instead of being maintained as a separate hard-coded value.
|
|
17
|
+
- **TLS 1.3 ECH + HelloRetryRequest** — Real ECH CH2 now reuses the ECH configuration, HPKE sender context, Inner random, GREASE values, extension permutation, and fake ECH bytes. HRR confirmation, selected groups, cookie-only retries, invalid extensions, dummy CCS, and local fatal alerts now follow the browser/RFC flow.
|
|
18
|
+
- **ECH + PSK + HRR Binders** — `pre_shared_key` remains exclusively in `ClientHelloInner`; CH1 and CH2 binders now use the correct truncated Inner transcripts, including `message_hash(ClientHelloInner1) + HRR` for CH2.
|
|
19
|
+
- **QUIC/H3 HRR State Transitions** — QUIC revokes 0-RTT stream and packet state after HRR and maps local TLS alerts to QUIC `CRYPTO_ERROR` codes.
|
|
20
|
+
|
|
21
|
+
## lkrequest-py 0.1.0
|
|
22
|
+
|
|
23
|
+
### 🚀 Features
|
|
24
|
+
- **TLS Fingerprint Control** — Byte-level ClientHello generation that matches real browsers (Chrome, Firefox, Safari)
|
|
25
|
+
- **HTTP/2 Fingerprint Control** — SETTINGS frame order, pseudo-header order, WINDOW_UPDATE, PRIORITY frames, and per-request priority weights
|
|
26
|
+
- **HTTP/3 + QUIC** — Optional `quic-h3` feature for full HTTP/3 over QUIC with fingerprint-aware Transport Parameters, Alt-Svc auto-discovery, and H2→H3 seamless upgrade
|
|
27
|
+
- **Built-in Browser Presets** — Chrome 131/144/145/146/147/148/149/150, Firefox 133/147, Safari 18/26 (TLS + H2, plus QUIC when `quic-h3` is enabled)
|
|
28
|
+
- **Session Management** — Cookie jars, connection pooling, HTTP/2 multiplexing, and optional QUIC 0-RTT session resumption
|
|
29
|
+
- **Connection Prewarming** — `session.preconnect()` pre-establishes DNS + TCP + TLS + H2 (or, with `quic-h3`, QUIC + H3) connections
|
|
30
|
+
- **Custom DNS Resolver** — Pluggable DNS with DoH support, auto ECH config and H3 hints via HTTPS/SVCB records
|
|
31
|
+
- **Alt-Svc Discovery** — Automatic HTTP/2 → HTTP/3 protocol upgrade via Alt-Svc headers with broken-QUIC fallback
|
|
32
|
+
- **SessionPool** — High-concurrency pool with proxy rotation (round-robin / random)
|
|
33
|
+
- **Proxy Support** — SOCKS5 and HTTP CONNECT with authentication
|
|
34
|
+
- **WebSocket** — HTTP/1.1 Upgrade (RFC 6455) and H2 Extended CONNECT (RFC 8441)
|
|
35
|
+
- **Middleware** — Interceptor chain for request/response modification
|
|
36
|
+
- **Retry Policies** — Exponential backoff, fixed interval, custom strategies; idempotency-aware (non-idempotent methods are not silently replayed after a possibly-already-sent failure unless marked `Idempotency::Idempotent`)
|
|
37
|
+
- **Redirect Control** — `RedirectPolicy::Follow(n)` or `RedirectPolicy::None` for manual handling
|
|
38
|
+
- **Auto-Decompression** — Brotli, gzip, deflate, zstd
|
|
39
|
+
- **Blocking API** — Synchronous wrapper for non-async contexts
|
|
40
|
+
- **Multipart/Form-Data** — File uploads with streaming support
|
|
41
|
+
- **TCP Fingerprint** — JA4T-style TCP SYN fingerprinting
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributing to lkrequest
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! This package is a [PyO3](https://pyo3.rs)
|
|
4
|
+
binding over the `lkrequest` Rust workspace, built with
|
|
5
|
+
[maturin](https://www.maturin.rs).
|
|
6
|
+
|
|
7
|
+
## Development setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/ez-opensource/lkrequest-py
|
|
11
|
+
cd lkrequest-py
|
|
12
|
+
python3 -m venv .venv # Linux/macOS
|
|
13
|
+
# py -m venv .venv # Windows
|
|
14
|
+
# Windows: .venv\Scripts\Activate.ps1
|
|
15
|
+
# Linux / macOS: source .venv/bin/activate
|
|
16
|
+
python -m pip install --upgrade pip
|
|
17
|
+
python -m pip install maturin
|
|
18
|
+
python -m pip install -e ".[test,lint]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Alternatively, use uv to create and manage the environment:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync --extra test --extra lint
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Optional build features:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
maturin develop --features telemetry # enable metrics_snapshot() counters
|
|
31
|
+
maturin develop --features quic-h3 # enable QUIC / HTTP3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Running the test suite
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m pip install -e ".[test]" # pytest + local server dependencies
|
|
38
|
+
just test
|
|
39
|
+
|
|
40
|
+
# With uv:
|
|
41
|
+
just test
|
|
42
|
+
# Or:
|
|
43
|
+
uv run pytest tests/ -v
|
|
44
|
+
|
|
45
|
+
# Skip the network/server tests and run only unit-level checks:
|
|
46
|
+
python -m pytest tests/ -v -k "not httpbin and not echo"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Before opening a pull request
|
|
50
|
+
|
|
51
|
+
The CI lint gate is strict — please run these locally and make sure they pass:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cargo fmt --all # format (CI runs `cargo fmt --check`)
|
|
55
|
+
cargo clippy -- -D warnings
|
|
56
|
+
python -m pytest tests/ -v
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A few project conventions:
|
|
60
|
+
|
|
61
|
+
- **Type stubs are the source of truth for the public API.** Any new public
|
|
62
|
+
class, method, or module function in Rust must also be declared in
|
|
63
|
+
`python/lkrequest/_lkrequest.pyi`. `tests/test_stub_parity.py` enforces this.
|
|
64
|
+
- **Avoid `unwrap()` / `expect()`** in non-test code (clippy is configured to
|
|
65
|
+
warn). Propagate errors with `?` and the project's error types, or add an
|
|
66
|
+
`#[allow(...)]` with a justifying comment where a panic is genuinely
|
|
67
|
+
unreachable (e.g. a `LazyLock` initializer).
|
|
68
|
+
- Keep new code formatted to match the surrounding style; `cargo fmt` handles
|
|
69
|
+
this for Rust.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
By contributing, you agree that your contributions will be licensed under the
|
|
74
|
+
[Apache License 2.0](LICENSE), consistent with the rest of the project.
|