Metadata-Version: 2.4
Name: async-rest-adapter
Version: 0.1.0
Summary: Async REST API adapter base class with retry, rate-limit handling, and attribution injection
Author-email: Tomas Amlov <tomasamlov@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/tomasamlov/async-rest-adapter
Project-URL: Repository, https://github.com/tomasamlov/async-rest-adapter
Keywords: async,rest,api,adapter,httpx,retry
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Framework :: AsyncIO
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-httpx>=0.30; extra == "dev"

# async-rest-adapter

A lightweight async REST API adapter base class built on [httpx](https://www.python-httpx.org/).

Provides a reusable `BaseAdapter` ABC that handles:
- Async HTTP client lifecycle (httpx.AsyncClient via context manager)
- Exponential-backoff retry on `httpx.RequestError`
- Rate-limit handling (HTTP 429 → sleep → retry)
- Typed error taxonomy (12 status-code → `APIError` mappings)
- Attribution injection into every successful response
- Structured `APIResponse` / `APIError` models (Pydantic v2)

## Installation

```bash
pip install async-rest-adapter
```

## Quick start

```python
from async_rest_adapter import BaseAdapter, APIResponse, Attribution

class MyAdapter(BaseAdapter):
    ATTRIBUTION = Attribution(
        license="CC-BY-4.0",
        text="My Data Source",
        url="https://example.com",
    )

    async def health_check(self) -> APIResponse:
        try:
            data = await self._make_request("GET", "/health")
            return self._wrap_response(True, data)
        except Exception as e:
            return self._wrap_response(False, error=str(e), error_type="health_check_failed")

async with MyAdapter("my-api", "https://api.example.com") as adapter:
    result = await adapter.health_check()
    print(result.success, result.data)
```

## API

### `BaseAdapter(provider_name, base_url, timeout=30.0, max_retries=3)`

Subclass and implement `health_check() -> APIResponse`.

**Key methods:**
- `_make_request(method, endpoint, **kwargs)` — HTTP request with retry, returns parsed body
- `_wrap_response(success, data, error, error_type, metadata)` — builds `APIResponse`, auto-injects `ATTRIBUTION`
- `_handle_rate_limit(response)` — sleep on 429, return True to retry
- `_handle_errors(response)` — map 4xx/5xx to `APIError`

**Test injection:**
```python
adapter.session = mock_client  # inject mock httpx client in tests
```

### `APIResponse`

```python
class APIResponse(BaseModel):
    success: bool
    data: Any | None
    error: str | None
    error_type: str | None
    metadata: dict  # includes "attribution" on every successful response
```

### `APIError(Exception)`

```python
raise APIError("Not found", status_code=404, error_type="not_found_error")
```

### `Attribution`

```python
Attribution(license="CC-BY-4.0", text="Source Name", url="https://example.com")
```

## Requirements

- Python 3.11+
- httpx ≥ 0.27
- pydantic ≥ 2.0

## License

MIT
