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.4-cp39-abi3-win_amd64.whl (3.1 MB view details)

Uploaded CPython 3.9+Windows x86-64

never_safari-0.5.4-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.4-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.4-cp39-abi3-macosx_11_0_arm64.whl (2.8 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

never_safari-0.5.4-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.4-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: never_safari-0.5.4-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.14.4

File hashes

Hashes for never_safari-0.5.4-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7976721bb95ed73bc6e7cddc356703198d55f8010a75d48856fdc4743be994fe
MD5 897b4cab43aaef89ca0c05927ddbb65a
BLAKE2b-256 a15737d2c84ef05962c3d9847ab81ed0615bea940f552f66ac8a360735fa843e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 2ad1e7fd86ba91709398901445e5c35ae743bbabe3e6ecafdb16edbb4967c217
MD5 05059043474b49185c206422196e30e8
BLAKE2b-256 69d5fba1ec442c14134036176d72caaa6b4f033d0a87412d8f773fef9506dd64

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b2a14252fd35b14dccb17e651f5b606aa0e56dc7f1521e83844cd5d9503c64ef
MD5 aee19e0af5529c5a062ff7df1cb9890e
BLAKE2b-256 454673b8a7f27ba73056a39c48826abb626f19ac735ca294da3df94871f2ac14

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.4-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fb95f99a4538deaa127c769f671116736ec400e1c8d2f4a34d3205283f9c1481
MD5 529e1c08107de192a84d1658c8ff99e9
BLAKE2b-256 5bff633a7c94e8b6228e96b399edac8b55769675edac18321609ea51b9f2560a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.4-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 4d343403a7ef3410942c5fad07af49be10c7fbfecaa70dbf7e306245e4c08e4c
MD5 c505a851b044f3f237ff12e9fa37d4c8
BLAKE2b-256 97ee86170b2fe6c079a6420e51117dd4cc2a0a03642f8d6cff4739d9eef2a600

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.4 This release

5 files

0.5.3

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