{"id":35035830,"url":"https://github.com/ackness/xttp","last_synced_at":"2026-05-22T03:09:12.370Z","repository":{"id":322744692,"uuid":"1090770942","full_name":"ackness/xttp","owner":"ackness","description":null,"archived":false,"fork":false,"pushed_at":"2025-11-06T05:41:41.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-06T06:01:53.446Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ackness.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-06T05:41:37.000Z","updated_at":"2025-11-06T05:41:44.000Z","dependencies_parsed_at":"2025-11-06T06:02:07.569Z","dependency_job_id":null,"html_url":"https://github.com/ackness/xttp","commit_stats":null,"previous_names":["ackness/xttp"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ackness/xttp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ackness%2Fxttp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ackness%2Fxttp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ackness%2Fxttp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ackness%2Fxttp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ackness","download_url":"https://codeload.github.com/ackness/xttp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ackness%2Fxttp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33326729,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"online","status_checked_at":"2026-05-22T02:00:06.671Z","response_time":265,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-27T07:55:19.598Z","updated_at":"2026-05-22T03:09:12.364Z","avatar_url":"https://github.com/ackness.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xttp\n\nA modern, production-ready async HTTP client for Python with fluent API, automatic retries, and intelligent rate limiting.\n\n## Features\n\n- **Fully Async**: Built on `httpx` for high-performance async I/O\n- **Fluent API**: Pythonic builder pattern for easy request construction\n- **Automatic Retry**: Exponential backoff with configurable retry strategies\n- **Smart Rate Limiting**: Unified configuration system supporting multiple platforms\n  - Built-in support: Cloudflare, GitHub, Twitter, Binance, Standard HTTP\n  - Custom headers: Define your own rate limit headers\n  - Custom parser: Write custom logic for complex rate limiting\n  - Priority-based: Multiple configs checked in priority order\n- **Comprehensive Error Handling**: Custom exceptions for different error types\n- **Sliding Window Rate Limiting**: Time window control for request throttling\n- **Context Manager Support**: Proper resource cleanup with async context managers\n- **Type Safe**: Full type hints for better IDE support\n- **Well Tested**: Comprehensive test suite with 88+ tests\n\n## Installation\n\n```bash\n# Using uv (recommended)\nuv add xttp\n\n# Using pip\npip install xttp\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nimport asyncio\nfrom xttp import HTTPClient\n\nasync def main():\n    async with HTTPClient(base_url=\"https://api.example.com\") as client:\n        # Simple GET request\n        response = await client.get(\"/users\")\n        print(response.json())\n\nasyncio.run(main())\n```\n\n### Fluent API (Recommended)\n\nThe fluent API provides a clean, chainable interface:\n\n```python\nasync with HTTPClient(base_url=\"https://api.example.com\") as client:\n    response = await (\n        client.request()\n        .set_query_param(\"search\", \"python\")\n        .set_query_param(\"size\", \"large\")\n        .set_header(\"User-Agent\", \"MyApp/1.0\")\n        .get(\"/search\")\n    )\n```\n\n## Advanced Usage\n\n### Rate Limiting\n\n```python\n# Limit to 100 requests per minute\n# Automatically respects rate limit headers from all common platforms\nasync with HTTPClient(\n    base_url=\"https://api.example.com\",\n    rate_limit=100,\n    rate_limit_window=60.0,\n) as client:\n    for i in range(200):\n        # Automatically rate limited\n        response = await client.get(f\"/item/{i}\")\n```\n\n### Custom Rate Limit Headers\n\n```python\nfrom xttp import HTTPClient, RateLimitConfig\n\n# Define custom rate limit headers for your API\ncustom_config = RateLimitConfig(\n    name=\"my-api\",\n    reset_headers=[\"x-custom-reset\"],\n    remaining_headers=[\"x-custom-remaining\"],\n    priority=10,\n)\n\nasync with HTTPClient(\n    base_url=\"https://api.example.com\",\n    rate_limit=100,\n    rate_limit_configs=[custom_config],\n) as client:\n    response = await client.get(\"/data\")\n```\n\n### Retry Configuration\n\n```python\nasync with HTTPClient(\n    base_url=\"https://api.example.com\",\n    max_retries=3,\n    retry_backoff_factor=2.0,  # Wait 1s, 2s, 4s between retries\n    retry_statuses=(408, 429, 500, 502, 503, 504),\n) as client:\n    response = await client.get(\"/unstable-endpoint\")\n```\n\n### Authentication\n\n```python\n# Basic auth\nresponse = await (\n    client.request()\n    .set_auth(\"username\", \"password\")\n    .get(\"/protected\")\n)\n```\n\n### POST with JSON\n\n```python\ndata = {\n    \"name\": \"Alice\",\n    \"email\": \"alice@example.com\"\n}\n\nresponse = await client.request().set_json(data).post(\"/users\")\n```\n\n### Custom Headers\n\n```python\n# Client-level headers (applied to all requests)\nasync with HTTPClient(\n    base_url=\"https://api.example.com\",\n    headers={\"X-API-Key\": \"secret123\"}\n) as client:\n    # Request-level headers (merged with client headers)\n    response = await (\n        client.request()\n        .set_header(\"X-Request-ID\", \"req-001\")\n        .get(\"/data\")\n    )\n```\n\n### Working with Cookies\n\n```python\nresponse = await (\n    client.request()\n    .set_cookie(\"session_id\", \"abc123\")\n    .set_cookies({\"user_pref\": \"dark_mode\"})\n    .get(\"/profile\")\n)\n```\n\n### Timeout Control\n\n```python\n# Per-request timeout\nresponse = await client.request().set_timeout(5.0).get(\"/slow-endpoint\")\n\n# Client-level default timeout\nasync with HTTPClient(timeout=10.0) as client:\n    response = await client.get(\"/endpoint\")\n```\n\n### Query Parameters\n\n```python\n# Multiple ways to set query parameters\n\n# Method 1: Fluent API\nresponse = await (\n    client.request()\n    .set_query_param(\"page\", \"1\")\n    .set_query_param(\"size\", \"20\")\n    .get(\"/items\")\n)\n\n# Method 2: Batch set\nresponse = await (\n    client.request()\n    .set_query_params({\"page\": \"1\", \"size\": \"20\"})\n    .get(\"/items\")\n)\n\n# Method 3: Direct method\nresponse = await client.get(\"/items\", params={\"page\": \"1\", \"size\": \"20\"})\n```\n\n## Error Handling\n\n```python\nfrom xttp import (\n    HTTPClientError,\n    RetryError,\n    TimeoutError,\n    ConnectionError,\n)\n\nasync with HTTPClient(base_url=\"https://api.example.com\") as client:\n    try:\n        response = await client.get(\"/endpoint\")\n        response.raise_for_status()\n    except RetryError as e:\n        print(f\"All retries exhausted: {e.message}\")\n    except TimeoutError as e:\n        print(f\"Request timed out: {e.message}\")\n    except ConnectionError as e:\n        print(f\"Connection failed: {e.message}\")\n    except HTTPClientError as e:\n        print(f\"HTTP error: {e.message}\")\n        if e.response:\n            print(f\"Status: {e.response.status_code}\")\n```\n\n## Platform-Specific Examples\n\n### Binance API\n\n```python\nfrom xttp import HTTPClient, BINANCE\n\nasync with HTTPClient(\n    base_url=\"https://api.binance.com\",\n    rate_limit=1200,  # Binance weight limit\n    rate_limit_window=60.0,\n    rate_limit_configs=[BINANCE],  # Use Binance-specific config\n) as client:\n    # Automatically respects X-MBX-USED-WEIGHT headers\n    response = await client.get(\"/api/v3/ticker/price\", params={\"symbol\": \"BTCUSDT\"})\n```\n\n### GitHub API\n\n```python\nfrom xttp import HTTPClient, GITHUB\n\nasync with HTTPClient(\n    base_url=\"https://api.github.com\",\n    headers={\"Accept\": \"application/vnd.github.v3+json\"},\n    rate_limit=5000,  # GitHub's rate limit\n    rate_limit_configs=[GITHUB],  # Use GitHub-specific config\n) as client:\n    # Automatically respects X-RateLimit-* headers\n    response = await client.get(\"/repos/python/cpython\")\n```\n\n### Multiple Platforms\n\n```python\nfrom xttp import HTTPClient, CLOUDFLARE, GITHUB, BINANCE\n\n# Support multiple platforms simultaneously\nasync with HTTPClient(\n    base_url=\"https://api.example.com\",\n    rate_limit=100,\n    rate_limit_configs=[CLOUDFLARE, GITHUB, BINANCE],\n) as client:\n    # Client will automatically detect and respect headers from any of these platforms\n    response = await client.get(\"/data\")\n```\n\n## API Reference\n\n### HTTPClient\n\n**Constructor Parameters:**\n- `base_url`: Base URL for all requests\n- `timeout`: Default timeout in seconds (default: 30.0)\n- `max_retries`: Maximum retry attempts (default: 3)\n- `retry_backoff_factor`: Exponential backoff factor (default: 2.0)\n- `retry_statuses`: HTTP status codes to retry (default: (408, 429, 500, 502, 503, 504))\n- `rate_limit`: Max requests per time window (default: None)\n- `rate_limit_window`: Time window for rate limiting in seconds (default: 60.0)\n- `rate_limit_configs`: List of RateLimitConfig instances (default: all common platforms)\n- `respect_rate_limit_headers`: Whether to respect rate limit headers from servers (default: True)\n- `headers`: Default headers for all requests\n- `verify`: Whether to verify SSL certificates (default: True)\n- `follow_redirects`: Whether to follow redirects (default: True)\n\n**Methods:**\n- `request()`: Create a new Request builder\n- `get(url, **kwargs)`: Execute GET request\n- `post(url, **kwargs)`: Execute POST request\n- `put(url, **kwargs)`: Execute PUT request\n- `patch(url, **kwargs)`: Execute PATCH request\n- `delete(url, **kwargs)`: Execute DELETE request\n- `head(url, **kwargs)`: Execute HEAD request\n- `options(url, **kwargs)`: Execute OPTIONS request\n- `close()`: Close the client and cleanup resources\n\n### Request Builder\n\n**Methods:**\n- `set_query_param(key, value)`: Set single query parameter\n- `set_query_params(params)`: Set multiple query parameters\n- `set_header(key, value)`: Set single header\n- `set_headers(headers)`: Set multiple headers\n- `set_cookie(key, value)`: Set single cookie\n- `set_cookies(cookies)`: Set multiple cookies\n- `set_json(data)`: Set JSON body\n- `set_form_data(data)`: Set form data\n- `set_body(data)`: Set raw body\n- `set_timeout(timeout)`: Set request timeout\n- `set_follow_redirects(follow)`: Set redirect behavior\n- `set_max_retries(retries)`: Set max retry attempts\n- `set_auth(username, password)`: Set basic authentication\n- `get(url)`: Execute GET request\n- `post(url)`: Execute POST request\n- `put(url)`: Execute PUT request\n- `patch(url)`: Execute PATCH request\n- `delete(url)`: Execute DELETE request\n- `head(url)`: Execute HEAD request\n- `options(url)`: Execute OPTIONS request\n\n### RateLimitConfig\n\nConfiguration for parsing rate limit headers from different platforms.\n\n**Parameters:**\n- `name`: Configuration name for identification\n- `reset_headers`: List of header names containing reset timestamp (Unix time)\n- `remaining_headers`: List of header names containing remaining request count\n- `limit_headers`: List of header names containing rate limit\n- `retry_after_headers`: List of header names containing retry delay in seconds\n- `custom_parser`: Optional function `(headers: dict) -\u003e Optional[float]` for custom parsing\n- `priority`: Priority level (higher = checked first, default: 0)\n\n**Predefined Configs:**\n- `CLOUDFLARE`: Cloudflare CDN (`cf-ratelimit-*`)\n- `GITHUB`: GitHub API (`x-ratelimit-*`)\n- `TWITTER`: Twitter API (`x-rate-limit-*`)\n- `BINANCE`: Binance exchange (`x-mbx-*`, weight-based)\n- `STANDARD_HTTP`: Standard HTTP headers (`x-ratelimit-*`, `retry-after`)\n\n**Example:**\n```python\nfrom xttp import RateLimitConfig\n\nconfig = RateLimitConfig(\n    name=\"my-api\",\n    reset_headers=[\"x-custom-reset\"],\n    remaining_headers=[\"x-custom-remaining\"],\n    priority=10,\n)\n```\n\n## Best Practices\n\n1. **Always use async context manager** to ensure proper cleanup:\n   ```python\n   async with HTTPClient(...) as client:\n       # Your code here\n   ```\n\n2. **Set appropriate rate limits** to avoid overwhelming APIs:\n   ```python\n   client = HTTPClient(rate_limit=100, rate_limit_window=60.0)\n   ```\n\n3. **Use default configs for common platforms**:\n   ```python\n   # No need to specify configs for common platforms\n   async with HTTPClient(rate_limit=100) as client:\n       # Automatically supports Cloudflare, GitHub, Binance, etc.\n       ...\n   ```\n\n4. **Specify platform configs for better performance**:\n   ```python\n   from xttp import HTTPClient, GITHUB\n\n   # If you know the platform, specify it\n   async with HTTPClient(rate_limit=5000, rate_limit_configs=[GITHUB]) as client:\n       ...\n   ```\n\n5. **Use custom configs for non-standard APIs**:\n   ```python\n   from xttp import RateLimitConfig\n\n   config = RateLimitConfig(\n       name=\"my-api\",\n       reset_headers=[\"x-custom-reset\"],\n       priority=10,\n   )\n   ```\n\n6. **Use fluent API for complex requests**:\n   ```python\n   response = await client.request().set_query_param(...).set_header(...).get(...)\n   ```\n\n7. **Handle errors appropriately**:\n   ```python\n   try:\n       response = await client.get(...)\n       response.raise_for_status()\n   except HTTPClientError as e:\n       # Handle error\n   ```\n\n8. **Set base_url** to avoid repetition:\n   ```python\n   client = HTTPClient(base_url=\"https://api.example.com\")\n   response = await client.get(\"/users\")  # Full URL: https://api.example.com/users\n   ```\n\n## Development\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/ackness/xttp.git\ncd xttp\n\n# Install dependencies (using uv)\nuv sync --dev\n```\n\n### Code Quality\n\nThe project uses `ruff` for linting and formatting:\n\n```bash\n# Format code\nuv run ruff format .\n\n# Check code\nuv run ruff check .\n\n# Fix auto-fixable issues\nuv run ruff check --fix .\n```\n\n### Testing\n\nThe project has a comprehensive test suite with 88+ tests covering:\n- HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)\n- Request builder (fluent API)\n- Retry mechanism with exponential backoff\n- Rate limiting with multiple platform configurations\n- Exception handling\n- Concurrent requests\n\n**Run tests:**\n\n```bash\n# Run all tests\nuv run pytest\n\n# Run with verbose output\nuv run pytest -v\n\n# Run specific test file\nuv run pytest tests/test_client.py\n\n# Run tests in parallel (faster)\nuv run pytest -n auto\n\n# Run with coverage report\nuv run pytest --cov=src --cov-report=html\nuv run pytest --cov=src --cov-report=term-missing\n```\n\n**Test structure:**\n```\ntests/\n├── conftest.py              # Shared fixtures\n├── test_client.py           # Basic HTTP client tests (21 tests)\n├── test_request_builder.py  # Fluent API tests (24 tests)\n├── test_retry.py            # Retry mechanism tests (12 tests)\n├── test_rate_limiter.py     # Rate limiting tests (10 tests)\n└── test_exceptions.py       # Exception handling tests (21 tests)\n```\n\nAll tests use `pytest-httpx` for mocking HTTP requests, ensuring fast and reliable test execution without making actual network calls.\n\n## Contributing\n\nContributions 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.\n\nPlease make sure to:\n1. Update tests as appropriate\n2. Run `uv run ruff format .` before committing\n3. Ensure all tests pass with `uv run pytest`\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fackness%2Fxttp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fackness%2Fxttp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fackness%2Fxttp/lists"}