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 + a from-scratch HTTP/2 layer.

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: DNS/Alt-Svc discovery, H3 preference, H2 fallback
    multiplex=True,       # default: concurrent requests share one connection
    timeout=30.0, connect_timeout=10.0, max_redirects=10,
)

print(r.http_version)   # "h2" or "h3"
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). Header names are lowercased for HTTP/2.
  • 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.0-cp39-abi3-win_amd64.whl (2.7 MB view details)

Uploaded CPython 3.9+Windows x86-64

never_safari-0.5.0-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.0-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.0-cp39-abi3-macosx_11_0_arm64.whl (2.4 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

never_safari-0.5.0-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.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for never_safari-0.5.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 6d7bdf67bf065426db8d4a851ff9b777fdd6f43c0d5702600bad49edfcd87690
MD5 c31d15579b7c4e6bf24319a51b692887
BLAKE2b-256 7ca22b38dc244e7231527784b44e717e560d5245e98fd760d9408851ee1b2ec8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b4408367a26ce603b5fd15f38ec05729396abea18ea33ab8b6bb974c3e33dbad
MD5 846d34365c6f12acf5918c733d6763c8
BLAKE2b-256 27e8060f0c6c23d6cefbab1228d45f303edd13c34978860fe39dc511a5e0d038

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 d3623beec6c4b179d022d4c1a07fdc6ee4245483ee6e2a019d4d44e39ea19954
MD5 850da5996cf97eb17bec441ac89afee7
BLAKE2b-256 87249b4abcd29a085c8efc4e52a96274c41b77c101d6b6a325d9cd909dec37ee

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fd728342c6844eda34ec79ea258821ab373abac7849e02d8f364b1013fa13c83
MD5 4623a19cec84b09b806b0584c2adede0
BLAKE2b-256 bdecab4794a2925ef6e3aaa43b63bba8f94a4f22398dab2d4533f54bbb2218de

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for never_safari-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 23ed948c9d11ef1ccbed18ed3af6ac105029896ff6ddf5dfb91a0af685b5099a
MD5 3c8379af5e38d71bf0b32f1c9158dc84
BLAKE2b-256 4a38e8d2d50707b84dc630f7e375a910f256af8bc09890942e6d33437df85e74

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

0.5.1

5 files

This release

0.5.0 This release

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