Skip to main content

never_safari (Python)

Send local HTTP requests with a byte-exact macOS Safari 26.5 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(
    ios=False,            # iOS Safari 26 profile (no post-quantum MLKEM768)
    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
    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=, timeout= (seconds, per-request), allow_redirects= (default True).
  • Headers: your headers= dict is merged over Safari's template — Safari's own headers keep their fixed (fingerprint) order and any you omit are auto-filled at their canonical position, while each custom header is placed right after whichever Safari header precedes it in your dict (so a captured request replays with its layout). Names are lowercased for H2/H3 and use Safari's captured canonical casing for HTTP/1.1.
  • 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.
  • 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.1-cp39-abi3-win_amd64.whl (2.7 MB view details)

Uploaded CPython 3.9+Windows x86-64

never_safari-0.5.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.7 MB view details)

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

never_safari-0.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (2.6 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

never_safari-0.5.1-cp39-abi3-macosx_11_0_arm64.whl (2.5 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

never_safari-0.5.1-cp39-abi3-macosx_10_12_x86_64.whl (2.6 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file never_safari-0.5.1-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 8fc511ba51b8cc23c263e2fac32d758b1027292c8771e88319d0ba5d4ed3264e
MD5 b713fc98bf7192f2bb729d85631d7050
BLAKE2b-256 4062c7dbfc6804ace3f7d4b181297ae3e1a9f89ed759c1e610610bfd29c9dffa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a7a438d16303fd45734dd5f2f2a9606135e098ffd59233c717270343d81073d8
MD5 c2df6a8c3aba81bcd969bb8ba82e04b5
BLAKE2b-256 f07e20833b397a51750162c05e30fe7cc70bced9936021fda8ee86c2fb83f450

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b65cf2c0b56d199ce818504e4f7beb1f484f53f61122c4c9904687220dfb834e
MD5 5d58a3100ec66f738ba241e96831ee3c
BLAKE2b-256 ca35db10ca9832496b52a4c847df645ebfc5dee9e1eeb29759e596e5533c4b08

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.1-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 850108919f45c497b3b62a98cb22117506e8622da2f8e8541f96fe7cebf48dd1
MD5 00c8278bfd01dd78cf0044607a1f4afa
BLAKE2b-256 b04a9eb5b47598b061b4e6a2653f38f63ce94ecb4ffd8ad6a595e2a633a3d359

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.1-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 2bc2546698b7e995dab37232782183f2c08777be6bded34b47cd39449c70d156
MD5 94044b5611b99169c8104f26f4876a2d
BLAKE2b-256 340ddaa59a3d159a9ea5209122a22fada8d7d23ab67059c098eddd6163c894c3

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.4

5 files

0.5.3

5 files

0.5.2

5 files

This release

0.5.1 This release

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