Skip to main content

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 the Session and at module level.
  • Request kwargs: params=, data= (dict → form / str / bytes), json= (any object), headers=, cookies=, referer=, mode= (auto, navigation, or fetch), destination= (auto, document, empty, or capture-validated image), top_level_url=, referrer_policy=, timeout= (seconds, per-request), allow_redirects= (default True). Use mode="navigation" for a top-level browser load and mode="fetch" for XHR. For a browser image request, pass destination="image" together with its top_level_url and referer. params accepts either a mapping or (name, value) pairs and expands sequence values. data and json are 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, while cookies= 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 (.status alias), .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.cookies for what a response set. For durable process/Redis persistence, store s.cookies.get_records() and restore each record with s.cookies.set_record(**record). This preserves complete scope and policy attributes; get_dict() is only a flattened view.
  • never_safari.HTTPError, Session (alias of Client).

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

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.

never_safari-0.5.3-cp39-abi3-win_amd64.whl (3.1 MB view details)

Uploaded CPython 3.9+Windows x86-64

never_safari-0.5.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB view details)

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

never_safari-0.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

never_safari-0.5.3-cp39-abi3-macosx_11_0_arm64.whl (2.8 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

never_safari-0.5.3-cp39-abi3-macosx_10_12_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

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

Hashes for never_safari-0.5.3-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 99fdbe1eda98ab2d3148e8428ea33faa9843c489cc15bfcf145e4ecfafae499e
MD5 44e75998291e00ef60fbe7620e9a4ebe
BLAKE2b-256 92bd3e01bbae7f307cc624d54a5112b4f1d9a87e7051b01f8d1457077ed5c6e7

See more details on using hashes here.

File details

Details for the file never_safari-0.5.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 27e05590774e1bcd18987c7f5b640e4dca586f6f4b6950093d7097a7254d1ea5
MD5 f4c80e1f805a4d770b5496d8bf713490
BLAKE2b-256 6dd788e8761f7f21a23a57164f1b7f9fcf5847f1a6996ec661cee69e7a539795

See more details on using hashes here.

File details

Details for the file never_safari-0.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3d313a7bc6de690b699ae3c05cfa098cca28113e2c6cc19918c7fdc83704cd91
MD5 a664e74793f7ecbf8cc09f8669549119
BLAKE2b-256 5514aaca96f86d5c6f0643157eb6306c686f311f4710fae980548db24be92495

See more details on using hashes here.

File details

Details for the file never_safari-0.5.3-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.3-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1b80b817cc043a4b27ab9088809d7f0c33998c10e8785ae72a1eee8999dc03d9
MD5 9ee966c3288c724421a018129b975630
BLAKE2b-256 123eb7268f8b3d42252ad72fd52c365717d44535865b91d0d802835081698e3b

See more details on using hashes here.

File details

Details for the file never_safari-0.5.3-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.3-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ca9c5cd9a7cf3a70bbbfc5d30799a9612d90b5c564e022d8932368c22ef26016
MD5 3c9fdc41ae2618d07378d380b46594e3
BLAKE2b-256 ac8fe5277eef5dd5dc0a28a141cb496465d16a046bf8da379b3ba63a94098d83

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.4

5 files

This release

0.5.3 This release

5 files

0.5.2

5 files

0.5.1

5 files

0.5.0

5 files

0.4.3

5 files

0.4.2

5 files

0.4.1

5 files

0.4.0

5 files

0.3.2

5 files

0.3.1

5 files

0.3.0

5 files

0.2.0

5 files

0.1.1

5 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page