Skip to main content

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.

CI PyPI Python 3.9+ Downloads

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

Requires Rust and maturin:

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pyureq-0.1.8-cp39-abi3-win_amd64.whl (553.9 kB view details)

Uploaded CPython 3.9+Windows x86-64

pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl (2.9 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.38+ x86-64

pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl (595.3 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

File details

Details for the file pyureq-0.1.8-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: pyureq-0.1.8-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 553.9 kB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pyureq-0.1.8-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 d9735d9149ee8eb8ec3542fa215b11b4e44ba9ff4f8eb2a9c37986013dd301fd
MD5 16de9c6d8b5a8a9598a25110bb0c8d81
BLAKE2b-256 a0c59f2b38ed5db2f374be146b90d594260e42b346355bb86427621355960066

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyureq-0.1.8-cp39-abi3-win_amd64.whl:

Publisher: ci.yml on vuongphu/pyureq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl.

File metadata

File hashes

Hashes for pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl
Algorithm Hash digest
SHA256 8ac57586bd4cab0761e948bec93dfb4346106f10f3918b72545fbd5da47939d5
MD5 842f9b0fbb34b10283617caf9f53f985
BLAKE2b-256 a25c83bf98a60802ef3f96cb99f2bb952fce8b86bdf3a5738de32e0388930f62

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyureq-0.1.8-cp39-abi3-manylinux_2_38_x86_64.whl:

Publisher: ci.yml on vuongphu/pyureq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d569e5edd65753d50fb15cba8f7fabdd8d74951df0b82bf3de863c7649dcd034
MD5 f1d402e95a4976f91e249812278caf3a
BLAKE2b-256 0a0a5a7bf1ef3f7ad0f2ef75f8fc97c19db8841f27c56c4c7d6a978446050b5d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyureq-0.1.8-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: ci.yml on vuongphu/pyureq

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.8 This release

3 files

0.1.7

5 files

0.1.6

3 files

0.1.5

3 files

0.1.4

3 files

0.1.3

3 files

0.1.2

3 files

0.1.1

3 files

0.1.0

3 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page