Async HTTP client with retry, rate limiting, and modern features
Project description
xttp
A modern, production-ready async HTTP client for Python with fluent API, automatic retries, and intelligent rate limiting.
Features
- Fully Async: Built on
httpxfor high-performance async I/O - Fluent API: Pythonic builder pattern for easy request construction
- Automatic Retry: Exponential backoff with configurable retry strategies
- Smart Rate Limiting: Unified configuration system supporting multiple platforms
- Built-in support: Cloudflare, GitHub, Twitter, Binance, Standard HTTP
- Custom headers: Define your own rate limit headers
- Custom parser: Write custom logic for complex rate limiting
- Priority-based: Multiple configs checked in priority order
- Comprehensive Error Handling: Custom exceptions for different error types
- Sliding Window Rate Limiting: Time window control for request throttling
- Context Manager Support: Proper resource cleanup with async context managers
- Type Safe: Full type hints for better IDE support
- Well Tested: Comprehensive test suite with 88+ tests
Installation
# Using uv (recommended)
uv add xttp
# Using pip
pip install xttp
Quick Start
Basic Usage
import asyncio
from xttp import HTTPClient
async def main():
async with HTTPClient(base_url="https://api.example.com") as client:
# Simple GET request
response = await client.get("/users")
print(response.json())
asyncio.run(main())
Fluent API (Recommended)
The fluent API provides a clean, chainable interface:
async with HTTPClient(base_url="https://api.example.com") as client:
response = await (
client.request()
.set_query_param("search", "python")
.set_query_param("size", "large")
.set_header("User-Agent", "MyApp/1.0")
.get("/search")
)
Advanced Usage
Rate Limiting
# Limit to 100 requests per minute
# Automatically respects rate limit headers from all common platforms
async with HTTPClient(
base_url="https://api.example.com",
rate_limit=100,
rate_limit_window=60.0,
) as client:
for i in range(200):
# Automatically rate limited
response = await client.get(f"/item/{i}")
Custom Rate Limit Headers
from xttp import HTTPClient, RateLimitConfig
# Define custom rate limit headers for your API
custom_config = RateLimitConfig(
name="my-api",
reset_headers=["x-custom-reset"],
remaining_headers=["x-custom-remaining"],
priority=10,
)
async with HTTPClient(
base_url="https://api.example.com",
rate_limit=100,
rate_limit_configs=[custom_config],
) as client:
response = await client.get("/data")
Retry Configuration
async with HTTPClient(
base_url="https://api.example.com",
max_retries=3,
retry_backoff_factor=2.0, # Wait 1s, 2s, 4s between retries
retry_statuses=(408, 429, 500, 502, 503, 504),
) as client:
response = await client.get("/unstable-endpoint")
Authentication
# Basic auth
response = await (
client.request()
.set_auth("username", "password")
.get("/protected")
)
POST with JSON
data = {
"name": "Alice",
"email": "alice@example.com"
}
response = await client.request().set_json(data).post("/users")
Custom Headers
# Client-level headers (applied to all requests)
async with HTTPClient(
base_url="https://api.example.com",
headers={"X-API-Key": "secret123"}
) as client:
# Request-level headers (merged with client headers)
response = await (
client.request()
.set_header("X-Request-ID", "req-001")
.get("/data")
)
Working with Cookies
response = await (
client.request()
.set_cookie("session_id", "abc123")
.set_cookies({"user_pref": "dark_mode"})
.get("/profile")
)
Timeout Control
# Per-request timeout
response = await client.request().set_timeout(5.0).get("/slow-endpoint")
# Client-level default timeout
async with HTTPClient(timeout=10.0) as client:
response = await client.get("/endpoint")
Query Parameters
# Multiple ways to set query parameters
# Method 1: Fluent API
response = await (
client.request()
.set_query_param("page", "1")
.set_query_param("size", "20")
.get("/items")
)
# Method 2: Batch set
response = await (
client.request()
.set_query_params({"page": "1", "size": "20"})
.get("/items")
)
# Method 3: Direct method
response = await client.get("/items", params={"page": "1", "size": "20"})
Error Handling
from xttp import (
HTTPClientError,
RetryError,
TimeoutError,
ConnectionError,
)
async with HTTPClient(base_url="https://api.example.com") as client:
try:
response = await client.get("/endpoint")
response.raise_for_status()
except RetryError as e:
print(f"All retries exhausted: {e.message}")
except TimeoutError as e:
print(f"Request timed out: {e.message}")
except ConnectionError as e:
print(f"Connection failed: {e.message}")
except HTTPClientError as e:
print(f"HTTP error: {e.message}")
if e.response:
print(f"Status: {e.response.status_code}")
Platform-Specific Examples
Binance API
from xttp import HTTPClient, BINANCE
async with HTTPClient(
base_url="https://api.binance.com",
rate_limit=1200, # Binance weight limit
rate_limit_window=60.0,
rate_limit_configs=[BINANCE], # Use Binance-specific config
) as client:
# Automatically respects X-MBX-USED-WEIGHT headers
response = await client.get("/api/v3/ticker/price", params={"symbol": "BTCUSDT"})
GitHub API
from xttp import HTTPClient, GITHUB
async with HTTPClient(
base_url="https://api.github.com",
headers={"Accept": "application/vnd.github.v3+json"},
rate_limit=5000, # GitHub's rate limit
rate_limit_configs=[GITHUB], # Use GitHub-specific config
) as client:
# Automatically respects X-RateLimit-* headers
response = await client.get("/repos/python/cpython")
Multiple Platforms
from xttp import HTTPClient, CLOUDFLARE, GITHUB, BINANCE
# Support multiple platforms simultaneously
async with HTTPClient(
base_url="https://api.example.com",
rate_limit=100,
rate_limit_configs=[CLOUDFLARE, GITHUB, BINANCE],
) as client:
# Client will automatically detect and respect headers from any of these platforms
response = await client.get("/data")
API Reference
HTTPClient
Constructor Parameters:
base_url: Base URL for all requeststimeout: Default timeout in seconds (default: 30.0)max_retries: Maximum retry attempts (default: 3)retry_backoff_factor: Exponential backoff factor (default: 2.0)retry_statuses: HTTP status codes to retry (default: (408, 429, 500, 502, 503, 504))rate_limit: Max requests per time window (default: None)rate_limit_window: Time window for rate limiting in seconds (default: 60.0)rate_limit_configs: List of RateLimitConfig instances (default: all common platforms)respect_rate_limit_headers: Whether to respect rate limit headers from servers (default: True)headers: Default headers for all requestsverify: Whether to verify SSL certificates (default: True)follow_redirects: Whether to follow redirects (default: True)
Methods:
request(): Create a new Request builderget(url, **kwargs): Execute GET requestpost(url, **kwargs): Execute POST requestput(url, **kwargs): Execute PUT requestpatch(url, **kwargs): Execute PATCH requestdelete(url, **kwargs): Execute DELETE requesthead(url, **kwargs): Execute HEAD requestoptions(url, **kwargs): Execute OPTIONS requestclose(): Close the client and cleanup resources
Request Builder
Methods:
set_query_param(key, value): Set single query parameterset_query_params(params): Set multiple query parametersset_header(key, value): Set single headerset_headers(headers): Set multiple headersset_cookie(key, value): Set single cookieset_cookies(cookies): Set multiple cookiesset_json(data): Set JSON bodyset_form_data(data): Set form dataset_body(data): Set raw bodyset_timeout(timeout): Set request timeoutset_follow_redirects(follow): Set redirect behaviorset_max_retries(retries): Set max retry attemptsset_auth(username, password): Set basic authenticationget(url): Execute GET requestpost(url): Execute POST requestput(url): Execute PUT requestpatch(url): Execute PATCH requestdelete(url): Execute DELETE requesthead(url): Execute HEAD requestoptions(url): Execute OPTIONS request
RateLimitConfig
Configuration for parsing rate limit headers from different platforms.
Parameters:
name: Configuration name for identificationreset_headers: List of header names containing reset timestamp (Unix time)remaining_headers: List of header names containing remaining request countlimit_headers: List of header names containing rate limitretry_after_headers: List of header names containing retry delay in secondscustom_parser: Optional function(headers: dict) -> Optional[float]for custom parsingpriority: Priority level (higher = checked first, default: 0)
Predefined Configs:
CLOUDFLARE: Cloudflare CDN (cf-ratelimit-*)GITHUB: GitHub API (x-ratelimit-*)TWITTER: Twitter API (x-rate-limit-*)BINANCE: Binance exchange (x-mbx-*, weight-based)STANDARD_HTTP: Standard HTTP headers (x-ratelimit-*,retry-after)
Example:
from xttp import RateLimitConfig
config = RateLimitConfig(
name="my-api",
reset_headers=["x-custom-reset"],
remaining_headers=["x-custom-remaining"],
priority=10,
)
Best Practices
-
Always use async context manager to ensure proper cleanup:
async with HTTPClient(...) as client: # Your code here
-
Set appropriate rate limits to avoid overwhelming APIs:
client = HTTPClient(rate_limit=100, rate_limit_window=60.0)
-
Use default configs for common platforms:
# No need to specify configs for common platforms async with HTTPClient(rate_limit=100) as client: # Automatically supports Cloudflare, GitHub, Binance, etc. ...
-
Specify platform configs for better performance:
from xttp import HTTPClient, GITHUB # If you know the platform, specify it async with HTTPClient(rate_limit=5000, rate_limit_configs=[GITHUB]) as client: ...
-
Use custom configs for non-standard APIs:
from xttp import RateLimitConfig config = RateLimitConfig( name="my-api", reset_headers=["x-custom-reset"], priority=10, )
-
Use fluent API for complex requests:
response = await client.request().set_query_param(...).set_header(...).get(...)
-
Handle errors appropriately:
try: response = await client.get(...) response.raise_for_status() except HTTPClientError as e: # Handle error
-
Set base_url to avoid repetition:
client = HTTPClient(base_url="https://api.example.com") response = await client.get("/users") # Full URL: https://api.example.com/users
Development
Setup
# Clone the repository
git clone https://github.com/ackness/xttp.git
cd xttp
# Install dependencies (using uv)
uv sync --dev
Code Quality
The project uses ruff for linting and formatting:
# Format code
uv run ruff format .
# Check code
uv run ruff check .
# Fix auto-fixable issues
uv run ruff check --fix .
Testing
The project has a comprehensive test suite with 88+ tests covering:
- HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
- Request builder (fluent API)
- Retry mechanism with exponential backoff
- Rate limiting with multiple platform configurations
- Exception handling
- Concurrent requests
Run tests:
# Run all tests
uv run pytest
# Run with verbose output
uv run pytest -v
# Run specific test file
uv run pytest tests/test_client.py
# Run tests in parallel (faster)
uv run pytest -n auto
# Run with coverage report
uv run pytest --cov=src --cov-report=html
uv run pytest --cov=src --cov-report=term-missing
Test structure:
tests/
├── conftest.py # Shared fixtures
├── test_client.py # Basic HTTP client tests (21 tests)
├── test_request_builder.py # Fluent API tests (24 tests)
├── test_retry.py # Retry mechanism tests (12 tests)
├── test_rate_limiter.py # Rate limiting tests (10 tests)
└── test_exceptions.py # Exception handling tests (21 tests)
All tests use pytest-httpx for mocking HTTP requests, ensuring fast and reliable test execution without making actual network calls.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to:
- Update tests as appropriate
- Run
uv run ruff format .before committing - Ensure all tests pass with
uv run pytest
License
MIT
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 xttp-0.1.0.tar.gz.
File metadata
- Download URL: xttp-0.1.0.tar.gz
- Upload date:
- Size: 31.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74695da75dc3dd142d7a8eee00439300c7c94caac52d0e008e839d891777d70e
|
|
| MD5 |
f3cee6db73c709cb30af45357ef56e93
|
|
| BLAKE2b-256 |
07b7995c3ef802a6d01637aea4a157e6cfd74427e1230e296cc44ae2639511f5
|
File details
Details for the file xttp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: xttp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47e9a9a0386fcc023d883509d56a9eed9d7a3feb07723eaf4eb4f98eb9179157
|
|
| MD5 |
57d086f7c4a14dd0cdae633f48d34a2f
|
|
| BLAKE2b-256 |
0ab7ab7644391351a7dbc77ba8bb0ec20ef4a41183d94127d0f22b0b74738d42
|