pyureq
A requests-compatible Python HTTP library backed by Rust's
ureq crate. Drop-in replacement for
requests — same API, lower overhead, no external runtime dependency.
Installation
pip install pyureq
Pre-built wheels are available for Linux, macOS, and Windows (Python 3.9–3.14). The wheel bundles its own OpenSSL, so no system dependency is required.
Quick start
import pyureq
# Simple GET
r = pyureq.get("https://httpbin.org/get", params={"q": "hello"})
print(r.status_code) # 200
print(r.json())
# POST JSON
r = pyureq.post("https://httpbin.org/post", json={"key": "value"})
print(r.json()["json"])
# POST form data
r = pyureq.post("https://httpbin.org/post", data={"field": "hello"})
print(r.json()["form"])
# With timeout and headers
r = pyureq.get(
"https://httpbin.org/get",
headers={"X-Custom": "header"},
timeout=5,
)
r.raise_for_status()
Alias as requests
Existing code that imports requests can be pointed at pyureq with a
one-line change:
import pyureq as requests # <- only change needed
r = requests.get("https://example.com")
print(r.text)
Sessions
pyureq.Session maps directly to requests.Session and shares the same
connection pool across requests, so repeated calls to the same host reuse a
single TCP connection:
import pyureq
with pyureq.Session() as s:
s.headers.update({"Authorization": "Bearer mytoken"})
s.timeout = 10
r = s.get("https://api.example.com/users")
r = s.post("https://api.example.com/items", json={"name": "widget"})
API reference
Top-level functions
| Function | Description |
|---|---|
pyureq.get(url, **kwargs) |
GET |
pyureq.post(url, data=None, json=None, **kwargs) |
POST |
pyureq.put(url, data=None, **kwargs) |
PUT |
pyureq.patch(url, data=None, **kwargs) |
PATCH |
pyureq.delete(url, **kwargs) |
DELETE |
pyureq.head(url, **kwargs) |
HEAD |
pyureq.options(url, **kwargs) |
OPTIONS |
pyureq.request(method, url, **kwargs) |
Any method |
pyureq.session() |
Create a Session |
Common keyword arguments
| Argument | Type | Description |
|---|---|---|
params |
dict or list[tuple] |
Query string parameters |
data |
dict, str, or bytes |
Form-encoded or raw request body |
json |
any JSON-serializable | JSON request body |
headers |
dict |
Extra HTTP headers |
cookies |
dict |
Cookies to send |
auth |
(user, password) |
HTTP Basic Auth |
timeout |
float |
Seconds before timing out |
allow_redirects |
bool |
Follow redirects (default True) |
verify |
bool |
Verify TLS certificate (default True) |
proxies |
dict |
Proxy URLs keyed by scheme — see Proxies |
Response attributes and methods
| Name | Description |
|---|---|
.status_code |
Integer status (e.g. 200) |
.headers |
Case-insensitive response headers |
.content |
Response body as bytes |
.text |
Response body decoded to str |
.encoding |
Encoding used for .text (readable/writable) |
.url |
Final URL after redirects |
.ok |
True if status < 400 |
.elapsed |
datetime.timedelta of round-trip time |
.json(**kwargs) |
Parse body as JSON |
.raise_for_status() |
Raise HTTPError on 4xx/5xx |
Proxies
Pass a proxies dict keyed by URL scheme, exactly as you would with requests:
PROXIES = {
"http": "http://proxy.example.com:8080",
"https": "http://user:pass@proxy.example.com:8080",
}
r = pyureq.get("https://api.example.com", proxies=PROXIES)
SOCKS proxies are supported too:
PROXIES = {
"http": "socks5://proxy.example.com:1080",
"https": "socks5://proxy.example.com:1080",
}
Use "all" as a catch-all key (same as requests):
pyureq.get(url, proxies={"all": "http://proxy:8080"})
Set proxies session-wide via Session.proxies; per-request proxies=
overrides individual schemes:
with pyureq.Session() as s:
s.proxies = {"https": "http://proxy:8080"}
s.get("https://api.example.com") # uses proxy
s.get("https://api.example.com", proxies={"https": ""}) # bypass proxy
Pass an empty dict or empty-string values to disable system proxies
(HTTP_PROXY / HTTPS_PROXY env vars):
pyureq.get(url, proxies={"http": "", "https": ""}) # no proxy
Exceptions
All exceptions live under pyureq.exceptions and mirror the requests
exception hierarchy:
RequestException
├── ConnectionError
│ └── ProxyError
│ └── SSLError
├── HTTPError
├── URLRequired
├── TooManyRedirects
├── Timeout
│ ├── ConnectTimeout
│ └── ReadTimeout
└── ...
import pyureq
from pyureq.exceptions import Timeout, HTTPError
try:
r = pyureq.get("https://example.com", timeout=2)
r.raise_for_status()
except Timeout:
print("Request timed out")
except HTTPError as e:
print(f"HTTP error: {e.response.status_code}")
Building from source
pip install maturin
maturin build --release
pip install target/wheels/*.whl
For a development install (rebuild-on-change):
pip install maturin
maturin develop
Running tests
pip install pytest flask
pytest tests/ -v
Benchmarks
All benchmarks run against a local server to eliminate network jitter.
Single-thread latency
Mean latency (ms) per request — lower is better.
Scenario pyureq requests curl_cffi httpx†
──────────────────────────────────────────────────────────────
GET /get 1.58 2.59 2.46 2.39
GET /get (params) 1.45 2.68 2.36 2.37
POST JSON 1.56 2.74 2.16 2.39
POST form 1.68 3.33 2.17 2.17
GET /status/200 1.24 2.43 2.03 1.85
GET /headers 1.44 2.72 2.37 2.37
──────────────────────────────────────────────────────────────
Overall mean 1.49 2.75 2.26 2.26
×1.0 ×1.85 slower ×1.52 slower ×1.52 slower
†httpx measured with Client() session; httpx.get() stateless has ~68 ms
per-call overhead (creates/destroys a full transport each time).
Concurrency — 1 000 simultaneous threads
Each thread makes 5 sequential requests (5 000 total). Higher req/s and lower p99 latency is better; fewer errors is better.
Mode Library req/s p99 ms errors
──────────────────────────────────────────────────
stateless pyureq 268 7 242 26 / 5000 ← fewest errors
curl_cffi 226 10 039 457 / 5000
requests 203 15 248 404 / 5000
session pyureq 261 9 206 46 / 5000
curl_cffi 232 10 038 450 / 5000
requests 244 10 015 327 / 5000
At 1 000 threads pyureq delivers 19 % more throughput and 17× fewer timeouts than curl_cffi in stateless mode. The advantage comes from lower Python-layer overhead per request: because pyureq's response parsing happens entirely in Rust, it holds the GIL for a shorter window (~0.3 ms vs ~0.8 ms), reducing GIL pile-up under heavy concurrency.
GIL behaviour
All four libraries release the GIL during network I/O — background Python threads can run while requests are in flight. The difference is how much Python overhead each library runs with the GIL held before and after each I/O call.
Library GIL held per request (approx)
──────────────────────────────────────────
pyureq ~0.3 ms (parsing in Rust)
curl_cffi ~0.8 ms (CFFI + Python result handling)
requests ~1.2 ms (urllib3 pure-Python stack)
Run the benchmarks yourself:
pip install requests httpx curl_cffi
python benchmarks/bench.py --iterations 500 # latency
python benchmarks/bench_threads.py --requests 5 # concurrency
python benchmarks/bench_gil.py --iterations 200 # GIL behaviour
How it works
Python call
│
▼
pyureq Python layer ← thin: arg normalisation, exception mapping
│
│ py.detach() ← GIL released here
▼
Rust / ureq ← HTTP request + response parsing, all in Rust
│
▼
native-tls ← system TLS on macOS/Windows, bundled OpenSSL on Linux
│
▼
TCP socket
The GIL is released for the entire Rust call — connection, TLS handshake,
send, recv, and response parsing all happen without blocking other Python
threads. The GIL is re-acquired only to build the final Python Response
object.
The Rust core uses ureq, a synchronous (no async runtime) HTTP/1.1 client built on Rust's standard blocking I/O.
License
MIT
Metadata
Release files for pyureq 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyureq-0.1.8-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details for pyureq-0.1.8-cp39-abi3-win_amd64.whl |
| pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.38+ x86-64 | Details for pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl |
| pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details for pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl |
Total release size: 4.1 MB
Release files / pyureq-0.1.8-cp39-abi3-win_amd64.whl
| Download URL | pyureq-0.1.8-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 553.9 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
d9735d9149ee8eb8ec3542fa215b11b4e44ba9ff4f8eb2a9c37986013dd301fd
|
|
BLAKE2b-256 checksum How to use checksums |
a0c59f2b38ed5db2f374be146b90d594260e42b346355bb86427621355960066
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl
| Download URL | pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | CPython 3.9 Linux glibc 2.38+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
8ac57586bd4cab0761e948bec93dfb4346106f10f3918b72545fbd5da47939d5
|
|
BLAKE2b-256 checksum How to use checksums |
a25c83bf98a60802ef3f96cb99f2bb952fce8b86bdf3a5738de32e0388930f62
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 595.3 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d569e5edd65753d50fb15cba8f7fabdd8d74951df0b82bf3de863c7649dcd034
|
|
BLAKE2b-256 checksum How to use checksums |
0a0a5a7bf1ef3f7ad0f2ef75f8fc97c19db8841f27c56c4c7d6a978446050b5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logSecurity for pyureq 0.1.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 3 files in this release have been uploaded with provenance metadata.
Attestation for pyureq-0.1.8-cp39-abi3-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.