Metadata-Version: 2.4
Name: async-requests-tls
Version: 0.1.0
Summary: Async HTTP/1.1 client whose TLS handshake is identical to the `requests` library.
Project-URL: Homepage, https://github.com/OleksandrShcherbinin/async-requests
Project-URL: Source, https://github.com/OleksandrShcherbinin/async-requests
Project-URL: Issues, https://github.com/OleksandrShcherbinin/async-requests/issues
Author: Oleksandr Shcherbinin
License: MIT License
        
        Copyright (c) 2026 Oleksandr Shcherbinin
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: async,fingerprint,http,ja3,requests,tls
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.13
Requires-Dist: urllib3>=2.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# async-requests-tls

Async Python HTTP/1.1 client whose TLS ClientHello is **byte-for-byte
identical** to what the [`requests`](https://pypi.org/project/requests/)
library sends — same JA3, same JA4. Useful when a site fingerprints the TLS
handshake and rejects modern async clients (`httpx`, `niquests`, …) while
happily accepting plain `requests`.

The trick: `requests` builds its `ssl.SSLContext` through
`urllib3.util.ssl_.create_urllib3_context()`. So do we. The context is then
fed into `asyncio.open_connection(..., ssl=ctx)`, so both stacks drive the
same OpenSSL build with the same cipher list, options, ALPN, curves, and
sigalgs.

## Install

```bash
pip install async-requests-tls
# or with uv:
uv add async-requests-tls
```

> The PyPI distribution name is `async-requests-tls` but the Python import
> name is `async_requests_tls`.

For development from a clone of this repo:

```bash
pip install -e .[dev]
# or:
uv sync --extra dev
```

Requires Python 3.13+ and `urllib3>=2.0`.

## Quickstart

```python
import asyncio
from async_requests_tls import AsyncSession

async def main():
    async with AsyncSession() as s:
        # GET
        r = await s.get("https://httpbin.org/get", params={"hello": "world"})
        print(r.status_code, r.json())

        # POST with JSON
        r = await s.post("https://httpbin.org/post", json={"x": 1})
        print(r.json())

        # Session-wide base_url, headers, and cookie jar
        async with AsyncSession(
            base_url="https://api.example.com",
            headers={"X-Token": "secret"},
        ) as api:
            await api.get("/users")          # GET https://api.example.com/users
            await api.post("/orders", data={"sku": "abc"})

asyncio.run(main())
```

`AsyncSession` supports `get` / `post` / `put` / `patch` / `delete` /
`head` / `options`, with a `requests`-style API: `params`, `headers`,
`data`, `json`, `cookies`, `allow_redirects`. Responses expose
`status_code`, `headers`, `content`, `text`, `json()`, `ok`, and
`cookies`.

## Caveats

- **HTTP/1.1 only.** ALPN defaults to `["http/1.1"]` so the server never
  upgrades us to h2. If you pass `alpn=alpn_protocols_like_urllib3()` (to
  match the *exact* ALPN bytes `requests` sends when urllib3-future is
  installed) and the server picks `h2`, the session raises
  `NotImplementedError` — the TLS handshake still matched, the wire
  protocol did not.
- **No connection pooling, no proxies, no streaming, no multipart, no
  HTTP/2.** This is a small, focused library.
- **JA3 parity ≠ detection-evasion parity.** Modern detectors also look at
  HTTP/2 SETTINGS frames, header order (JA4_H), and Akamai-style timing.
  This library only matches the TLS layer.

## Testing

```bash
uv run pytest        # or: pytest
```

All tests are unit tests — they don't open real sockets. The wire-level
tests drive `asyncio.StreamReader` with canned bytes; the session tests
patch `_send_once` with a fake.

## Public API

```python
from async_requests_tls import (
    AsyncSession,
    Response,
    build_ssl_context,            # if you want the SSLContext directly
    alpn_protocols_like_urllib3,  # exact ALPN list urllib3 would send
)
```

## License

MIT — see `LICENSE`.
