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

Uploaded CPython 3.9+Windows x86-64

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

Uploaded CPython 3.9+macOS 11.0+ ARM64

never_safari-0.5.2-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.2-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: never_safari-0.5.2-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.2-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 abff97502836864b6e0d16a8ed7aac217234a55e07fcaef22c2022460198fee0
MD5 4b2e889ac7a6de295378f7d69b037424
BLAKE2b-256 414e8fbe4200f4026945f8e0caf508603ecc17dfefd7713c2f46c306dd175679

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 910db3c34e3885931d3dab40ce86289248ae157c88780f796f58640f31085999
MD5 f60387e310a89040a4b4a2bb451896de
BLAKE2b-256 d7f92940d285739aa9201f42010fb4b4094e5a9bb1aef021bd86d4aef28b2dd9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 c8b0878e3f58c297e6f0a3fbc2477cc4e3310811329f045c4a6ae39e7f138a9f
MD5 e1e253a53472e7ba186b164977bc7a76
BLAKE2b-256 075d62ef69b1a9314df65c41148af5da8151e85789a1ae190ccec742e301a451

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.2-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5c47270e450b302a1ee306783a3d178f73312516ceca598bbd92d962346cc45e
MD5 fb1b94e94aae599c04eccf79a2903881
BLAKE2b-256 4ae24f66d228b53c101fee44e6ccf558463ff31d3d99da75eedccd632dea82ad

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.2-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 8d73a2c3e882569dbfa886c68474135b42a8b0ffb5356856dced15c5caf948a0
MD5 245e763016ff0ee63ac29cbefe847a27
BLAKE2b-256 55e1bebb8d073c000e6fcdb0c0da4ceaa0c885207dcd8d250a0a7e06a00cc030

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.4

5 files

0.5.3

5 files

This release

0.5.2 This release

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