never_safari (Python)
Send local HTTP requests with a capture-validated macOS Safari 26.5.2 static
network fingerprint — TLS ClientHello (JA3/JA4), HTTP/2 (SETTINGS/WINDOW_UPDATE/
pseudo-header order), HPACK (Huffman + dynamic table), request-header order,
download flow-control cadence, and TLS-record framing — like curl_cffi, but for
Safari. Built on a patched BoringSSL plus from-scratch HTTP/1.1 and HTTP/2
layers, with opportunistic HTTP/3 and automatic protocol fallback.
Install
pip install never_safari # prebuilt wheels: macOS / Linux / Windows
# or from source (needs cmake, go, ninja, a C/C++ compiler):
pip install maturin && maturin develop -m crates/safari-py/Cargo.toml
Use — a requests-style API
import never_safari
# Session keeps cookies + a connection pool (like a browser). `Client` is an alias.
s = never_safari.Session()
r = s.get("https://tls.peet.ws/api/all", params={"pretty": "1"})
print(r.status_code, r.ok)
print(r.json()["tls"]["ja4"]) # t13d2013h2_a09f3c656075_7f0f34a4126d
# POST: json= takes a Python object; data= takes a dict (form) / str / bytes.
r = s.post("https://api.example.com/v1/items",
json={"name": "x"},
headers={"authorization": "Bearer TOKEN"})
r.raise_for_status() # raises never_safari.HTTPError on 4xx/5xx
# Module-level, exactly like requests:
never_safari.get("https://example.com", params={"q": "1"})
never_safari.post("https://api.example.com", data={"k": "v"}, timeout=10)
# Session options
s = never_safari.Session(
verify=True, # TLS cert verification (like requests)
proxies={"https": "socks5://user:pass@host:1080"}, # or HTTP CONNECT
http3=True, # default: H3 preference, then ALPN-selected H2/H1.1
multiplex=True, # default: concurrent requests share one connection
accept_language="en-US,en;q=0.9", # match the proxy/browser locale
timeout=30.0, connect_timeout=30.0, idle_timeout=60.0, max_redirects=10,
)
print(r.http_version) # "h3", "h2", or "http/1.1"
print(s.resource_stats) # live request/dial/cache counters
# `with never_safari.Session(...) as s:` or `s.close()` releases them promptly.
# Crawler resource limits are enabled automatically; constructor overrides are
# available only when a workload needs non-default tuning.
# After an OS/network change, validate and switch a live H3 path:
new_local_address = s.migrate_http3("https://example.com")
Matches requests' surface:
- Verbs:
get/post/put/patch/delete/head/options/request— on theSessionand at module level. - Request kwargs:
params=,data=(dict → form / str / bytes),json=(any object),headers=,cookies=,referer=,mode=(auto,navigation, orfetch),destination=(auto,document,empty, or capture-validatedimage),top_level_url=,referrer_policy=,timeout=(seconds, per-request),allow_redirects=(defaultTrue). Usemode="navigation"for a top-level browser load andmode="fetch"for XHR. For a browser image request, passdestination="image"together with itstop_level_urlandreferer.paramsaccepts either a mapping or(name, value)pairs and expands sequence values.dataandjsonare mutually exclusive; unsupported body types raise instead of silently sending an empty body. - Headers: supplied values are authoritative. Known Safari fields are
checked and automatically reordered into the captured profile; only omitted
fields receive defaults. Custom fields retain caller order/anchors. An
explicit
headers={"cookie": ...}replaces the jar for that request, whilecookies=augments it. Jar cookies are joined into one field on H1 and split into one incrementally indexed field per pair on H2, matching live Safari; an explicit Cookie remains intact. Names are lowercased for H2/H3 and use Safari's captured canonical casing for HTTP/1.1. Connection-specific/Host fields are removed for protocol correctness; an explicit Content-Length must equal the actual body length. Response:.status_code(.statusalias),.ok,.reason,.headers,.content,.text,.json(),.url,.resumed,.cookies,.raise_for_status().- Cookies (dict-like, like curl_cffi / httpx):
s.cookies["k"],s.cookies.get("k", domain=…, path=…),.get_dict(domain=…, path=…),.set("k", "v", domain=…, path=…),.delete("k", domain=…, path=…),.clear(domain=…, path=…),.update(),.keys()/.values()/.items(),"k" in s.cookies,dict(s.cookies);r.cookiesfor what a response set. For durable process/Redis persistence, stores.cookies.get_records()and restore each record withs.cookies.set_record(**record). This preserves complete scope and policy attributes;get_dict()is only a flattened view. never_safari.HTTPError,Session(alias ofClient).
The network call releases the GIL, so many Python threads can crawl concurrently.
Async — AsyncSession (like curl_cffi)
import asyncio, never_safari
async def main():
async with never_safari.AsyncSession() as s: # max_workers= bounds concurrency
r = await s.get("https://example.com", params={"q": "1"})
r = await s.post(url, json={"a": 1})
results = await asyncio.gather(*[s.get(u) for u in urls]) # runs concurrently
asyncio.run(main())
Same verbs, kwargs, Response and .cookies as the sync Session; every verb is
a coroutine. It runs the (GIL-releasing) sync core in a bounded thread pool, so
requests run genuinely in parallel. AsyncClient is an alias.
See the project README for the full fingerprint details and scope.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file never_safari-0.5.3-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: never_safari-0.5.3-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 3.1 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99fdbe1eda98ab2d3148e8428ea33faa9843c489cc15bfcf145e4ecfafae499e
|
|
| MD5 |
44e75998291e00ef60fbe7620e9a4ebe
|
|
| BLAKE2b-256 |
92bd3e01bbae7f307cc624d54a5112b4f1d9a87e7051b01f8d1457077ed5c6e7
|
File details
Details for the file never_safari-0.5.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: never_safari-0.5.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.1 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27e05590774e1bcd18987c7f5b640e4dca586f6f4b6950093d7097a7254d1ea5
|
|
| MD5 |
f4c80e1f805a4d770b5496d8bf713490
|
|
| BLAKE2b-256 |
6dd788e8761f7f21a23a57164f1b7f9fcf5847f1a6996ec661cee69e7a539795
|
File details
Details for the file never_safari-0.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: never_safari-0.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.0 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d313a7bc6de690b699ae3c05cfa098cca28113e2c6cc19918c7fdc83704cd91
|
|
| MD5 |
a664e74793f7ecbf8cc09f8669549119
|
|
| BLAKE2b-256 |
5514aaca96f86d5c6f0643157eb6306c686f311f4710fae980548db24be92495
|
File details
Details for the file never_safari-0.5.3-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: never_safari-0.5.3-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b80b817cc043a4b27ab9088809d7f0c33998c10e8785ae72a1eee8999dc03d9
|
|
| MD5 |
9ee966c3288c724421a018129b975630
|
|
| BLAKE2b-256 |
123eb7268f8b3d42252ad72fd52c365717d44535865b91d0d802835081698e3b
|
File details
Details for the file never_safari-0.5.3-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: never_safari-0.5.3-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.0 MB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca9c5cd9a7cf3a70bbbfc5d30799a9612d90b5c564e022d8932368c22ef26016
|
|
| MD5 |
3c9fdc41ae2618d07378d380b46594e3
|
|
| BLAKE2b-256 |
ac8fe5277eef5dd5dc0a28a141cb496465d16a046bf8da379b3ba63a94098d83
|