Modern Python wrapper for tls-client with httpx-like API and browser fingerprinting
Project description
tlshttp
Modern Python wrapper for tls-client with an httpx-like API and browser TLS fingerprinting.
Features
- httpx-like API - Familiar
Client/AsyncClientpattern - Browser TLS fingerprinting - 50+ profiles (Chrome, Firefox, Safari, Opera)
- Auto-download binaries - No manual setup required
- True async support - Using anyio (works with asyncio and trio)
- Fixes common wrapper bugs:
cookies.clear()actually works- No memory leaks (proper cleanup with context managers)
- Case-insensitive headers (RFC 7230 compliant)
Installation
pip install tlshttp
Or with uv:
uv add tlshttp
Quick Start
Synchronous
import tlshttp
with tlshttp.Client() as client:
response = client.get("https://httpbin.org/get")
print(response.json())
Asynchronous
import asyncio
import tlshttp
async def main():
async with tlshttp.AsyncClient() as client:
response = await client.get("https://httpbin.org/get")
print(response.json())
asyncio.run(main())
Client Configuration
client = tlshttp.Client(
# Browser fingerprint profile
profile="chrome_120", # Default
# Request defaults
timeout=30.0,
follow_redirects=True,
# Proxy support
proxy="http://user:pass@proxy.example.com:8080",
# TLS settings
verify=True, # Verify SSL certificates
http2=True, # Enable HTTP/2
# Default headers for all requests
headers={"User-Agent": "MyApp/1.0"},
# Default cookies
cookies={"session": "abc123"},
# Base URL for relative paths
base_url="https://api.example.com",
)
HTTP Methods
All standard HTTP methods are supported:
with tlshttp.Client() as client:
# GET
response = client.get("https://httpbin.org/get")
# POST with JSON
response = client.post(
"https://httpbin.org/post",
json={"key": "value"}
)
# POST with form data
response = client.post(
"https://httpbin.org/post",
data={"username": "john", "password": "secret"}
)
# PUT
response = client.put(
"https://httpbin.org/put",
json={"updated": True}
)
# PATCH
response = client.patch(
"https://httpbin.org/patch",
json={"field": "new_value"}
)
# DELETE
response = client.delete("https://httpbin.org/delete")
# HEAD
response = client.head("https://httpbin.org/get")
# OPTIONS
response = client.options("https://httpbin.org/get")
Request Parameters
Query Parameters
response = client.get(
"https://httpbin.org/get",
params={"page": 1, "limit": 10}
)
# Requests: https://httpbin.org/get?page=1&limit=10
Headers
response = client.get(
"https://httpbin.org/headers",
headers={"Authorization": "Bearer token123"}
)
JSON Body
response = client.post(
"https://httpbin.org/post",
json={"message": "Hello", "count": 42}
)
Form Data
response = client.post(
"https://httpbin.org/post",
data={"username": "john", "password": "secret"}
)
Raw Content
response = client.post(
"https://httpbin.org/post",
content=b"raw bytes here"
)
Timeout
# Per-request timeout (overrides client default)
response = client.get(
"https://httpbin.org/delay/5",
timeout=10.0
)
Authentication
# Basic auth - httpx-style tuple
response = client.get(
"https://httpbin.org/basic-auth/user/pass",
auth=("user", "pass")
)
Response Object
response = client.get("https://httpbin.org/get")
# Status
response.status_code # 200
response.is_success # True (2xx)
response.is_redirect # False (3xx)
response.is_client_error # False (4xx)
response.is_server_error # False (5xx)
response.is_error # False (4xx or 5xx)
# Content
response.content # Raw bytes
response.text # Decoded string (auto-detects encoding)
response.json() # Parse as JSON
# Headers (case-insensitive)
response.headers["Content-Type"]
response.headers["content-type"] # Same value
response.headers.get("X-Custom", "default")
# Cookies from response
response.cookies["session_id"]
# URL (final URL after redirects)
response.url
# HTTP version
response.http_version # "HTTP/2" or "HTTP/1.1"
# Raise exception for error status codes
response.raise_for_status() # Raises HTTPStatusError for 4xx/5xx
Cookies
Automatic Cookie Handling
Cookies are automatically stored and sent with subsequent requests:
with tlshttp.Client() as client:
# Server sets a cookie
client.get("https://httpbin.org/cookies/set/session/abc123")
# Cookie is automatically sent
response = client.get("https://httpbin.org/cookies")
print(response.json()) # {"cookies": {"session": "abc123"}}
Manual Cookie Management
# Set cookies on client
client.cookies["token"] = "xyz789"
client.cookies.set("user", "john", domain="example.com")
# Get cookies
session = client.cookies.get("session")
# Clear all cookies (actually works, unlike other wrappers!)
client.cookies.clear()
# Clear cookies for specific domain
client.cookies.clear(domain="example.com")
# Iterate cookies
for name, value in client.cookies.items():
print(f"{name}: {value}")
# Export to dict
all_cookies = client.cookies.to_dict()
Headers
Case-Insensitive Access
HTTP headers are case-insensitive per RFC 7230. tlshttp handles this correctly:
from tlshttp import Headers
headers = Headers({"Content-Type": "application/json"})
# All of these return the same value
headers["Content-Type"]
headers["content-type"]
headers["CONTENT-TYPE"]
Client Default Headers
client = tlshttp.Client(
headers={"User-Agent": "MyBot/1.0", "Accept": "application/json"}
)
# Per-request headers merge with (and override) client defaults
response = client.get(
"https://httpbin.org/headers",
headers={"Accept": "text/html"} # Overrides Accept, keeps User-Agent
)
Browser Profiles
tlshttp includes 50+ browser TLS fingerprint profiles that mimic real browsers:
Chrome
client = tlshttp.Client(profile="chrome_120") # Recommended default
client = tlshttp.Client(profile="chrome_131") # Newest
client = tlshttp.Client(profile="chrome_103") # Oldest supported
Available: chrome_103 through chrome_131, plus PSK variants like chrome_120_psk
Firefox
client = tlshttp.Client(profile="firefox_120")
client = tlshttp.Client(profile="firefox_133") # Newest
client = tlshttp.Client(profile="firefox_102") # Oldest
Available: firefox_102 through firefox_133
Safari
client = tlshttp.Client(profile="safari_16_0")
client = tlshttp.Client(profile="safari_ios_16_0")
client = tlshttp.Client(profile="safari_ios_17_0")
Opera
client = tlshttp.Client(profile="opera_89")
client = tlshttp.Client(profile="opera_90")
client = tlshttp.Client(profile="opera_91")
List All Profiles
from tlshttp.profiles import BROWSER_PROFILES
for name in sorted(BROWSER_PROFILES.keys()):
print(name)
Async Client
The AsyncClient has the same API as Client, but all methods are async:
import asyncio
import tlshttp
async def main():
async with tlshttp.AsyncClient(profile="firefox_120") as client:
# Single request
response = await client.get("https://httpbin.org/get")
# Concurrent requests
urls = [
"https://httpbin.org/get?id=1",
"https://httpbin.org/get?id=2",
"https://httpbin.org/get?id=3",
]
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
for response in responses:
print(response.json()["args"]["id"])
asyncio.run(main())
Concurrency Limit
Control the maximum number of concurrent requests:
# Limit to 5 concurrent requests (default: 10)
client = tlshttp.AsyncClient(max_concurrent=5)
Proxy Support
# HTTP proxy
client = tlshttp.Client(proxy="http://proxy.example.com:8080")
# With authentication
client = tlshttp.Client(proxy="http://user:pass@proxy.example.com:8080")
# SOCKS5 proxy
client = tlshttp.Client(proxy="socks5://proxy.example.com:1080")
Error Handling
import tlshttp
try:
with tlshttp.Client(timeout=5.0) as client:
response = client.get("https://httpbin.org/delay/10")
response.raise_for_status()
except tlshttp.TimeoutError:
print("Request timed out")
except tlshttp.ConnectError:
print("Failed to connect")
except tlshttp.HTTPStatusError as e:
print(f"HTTP {e.response.status_code}: {e.response.text}")
except tlshttp.RequestError as e:
print(f"Request failed: {e}")
Exception Hierarchy
TLSHTTPError (base)
├── RequestError
│ ├── ConnectError
│ ├── TimeoutError
│ └── ProxyError
└── HTTPStatusError (raised by raise_for_status())
Comparison with Other Libraries
| Feature | tlshttp | requests | httpx | curl_cffi | tls-client |
|---|---|---|---|---|---|
| TLS Fingerprinting | ✅ 50+ | ❌ | ❌ | ✅ | ✅ |
| Async Support | ✅ | ❌ | ✅ | ✅ | ❌ |
| HTTP/2 | ✅ | ❌ | ✅ | ✅ | ✅ |
| httpx-like API | ✅ | ❌ | ✅ | ❌ | ❌ |
| Cookie clear() works | ✅ | ✅ | ✅ | ✅ | ❌ |
| No memory leaks | ✅ | ✅ | ✅ | ✅ | ❌ |
| Auto-download binary | ✅ | N/A | N/A | ✅ | ❌ |
How It Works
tlshttp wraps the bogdanfinn/tls-client Go library via ctypes. On first use, it automatically downloads the appropriate binary for your platform from the tls-client GitHub releases.
The Go library provides:
- Accurate browser TLS fingerprints (JA3, JA4, HTTP/2 settings, header order)
- HTTP/2 and HTTP/3 support
- Connection pooling
tlshttp adds:
- Pythonic httpx-like API
- Proper memory management (context managers, weak reference finalizers)
- Case-insensitive headers (RFC 7230)
- True async support via anyio thread pool
- Bug fixes for issues in other wrappers
Platform Support
| Platform | Architecture | Supported |
|---|---|---|
| Linux | x86_64 | ✅ |
| Linux | ARM64 | ✅ |
| Linux (Alpine/musl) | x86_64 | ✅ |
| macOS | x86_64 (Intel) | ✅ |
| macOS | ARM64 (M1/M2/M3) | ✅ |
| Windows | x86_64 | ✅ |
| Windows | x86 (32-bit) | ✅ |
Development
# Clone
git clone https://github.com/Sekinal/tlshttp.git
cd tlshttp
# Install with dev dependencies
uv sync --group dev
# Run all tests
uv run pytest
# Skip integration tests (no network needed)
uv run pytest -m "not integration"
# Run specific test file
uv run pytest tests/test_headers.py -v
# Run with coverage
uv run pytest --cov=tlshttp
License
MIT License - see LICENSE for details.
This project uses binaries from tls-client which is licensed under BSD-4-Clause.
Credits
- bogdanfinn/tls-client - The Go TLS client library
- httpx - API design inspiration
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tlshttp-0.2.0.tar.gz.
File metadata
- Download URL: tlshttp-0.2.0.tar.gz
- Upload date:
- Size: 42.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eba6031d0aa5db65f1d8158b7db77e9007f70029a35c662b377d8a3574a20189
|
|
| MD5 |
3f770137b65a0ac344b64fc530b780c9
|
|
| BLAKE2b-256 |
820f6bdc4d2c6cbd75c1e5c8b886b5b36fb8a218de0124ce1ad082a15f40deba
|
Provenance
The following attestation bundles were made for tlshttp-0.2.0.tar.gz:
Publisher:
publish.yml on Sekinal/tlshttp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tlshttp-0.2.0.tar.gz -
Subject digest:
eba6031d0aa5db65f1d8158b7db77e9007f70029a35c662b377d8a3574a20189 - Sigstore transparency entry: 807042720
- Sigstore integration time:
-
Permalink:
Sekinal/tlshttp@7d79ee997052674a0759e44dd4cebb9435778c50 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Sekinal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d79ee997052674a0759e44dd4cebb9435778c50 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tlshttp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: tlshttp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 32.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd64e6ccc618f4befed95d330fc1e3d5856451e9dcaec6b859ddf936530cc83a
|
|
| MD5 |
822f895401721c00d2be113b37a7fb64
|
|
| BLAKE2b-256 |
356adaf84ccb1a2e9e812abbf84cdd2113276c72ae22cdf14d13f97364dbde2c
|
Provenance
The following attestation bundles were made for tlshttp-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on Sekinal/tlshttp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tlshttp-0.2.0-py3-none-any.whl -
Subject digest:
cd64e6ccc618f4befed95d330fc1e3d5856451e9dcaec6b859ddf936530cc83a - Sigstore transparency entry: 807042738
- Sigstore integration time:
-
Permalink:
Sekinal/tlshttp@7d79ee997052674a0759e44dd4cebb9435778c50 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Sekinal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d79ee997052674a0759e44dd4cebb9435778c50 -
Trigger Event:
release
-
Statement type: