Metadata-Version: 2.4
Name: never_safari
Version: 0.4.1
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Intended Audience :: Developers
Summary: Send local HTTP requests with a byte-exact macOS Safari 26.5 network fingerprint (TLS/HTTP2/HPACK/headers).
Keywords: safari,tls,http2,fingerprint,ja4,anti-detection,scraping
Author: never_safari contributors
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/neverl805/never_safari

# 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

```sh
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

```python
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": "http://user:pass@host:8080"},
    multiplex=True,       # browser-style: concurrent requests share one connection
    timeout=30.0, connect_timeout=10.0, max_redirects=10,
)
```

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=`.
- **`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)

```python
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](https://github.com/neverl805/never_safari) for the full
fingerprint details and scope.

