Metadata-Version: 2.4
Name: ahttp_client
Version: 2.0.0b2
Summary: A framework for easy asynchronous HTTP request calling with decorations
Author-email: gunyu1019 <gunyu1019@yhs.kr>
License: MIT License
        
        Copyright (c) 2023-present gunyu1019
        
        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.
Project-URL: Homepage, https://github.com/gunyu1019/ahttp-client
Project-URL: Issue Tracker, https://github.com/gunyu1019/ahttp-client/issues
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: Korean
Classifier: Natural Language :: English
Classifier: Programming Language :: Python
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
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pydantic
Requires-Dist: pydantic>=2.12; extra == "pydantic"
Provides-Extra: marshmallow
Requires-Dist: marshmallow>=3; extra == "marshmallow"
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.11.2; extra == "aiohttp"
Provides-Extra: httpx
Requires-Dist: httpx>=0.28.1; extra == "httpx"
Provides-Extra: requests
Requires-Dist: requests>=2.32.3; extra == "requests"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Requires-Dist: aiohttp>=3.11.2; extra == "test"
Provides-Extra: lint
Requires-Dist: pycodestyle; extra == "lint"
Requires-Dist: black; extra == "lint"
Provides-Extra: docs
Requires-Dist: Sphinx; extra == "docs"
Requires-Dist: sphinxawesome-theme; extra == "docs"
Requires-Dist: sphinx-intl; extra == "docs"
Dynamic: license-file

# ahttp-client

![PyPI - Version](https://img.shields.io/pypi/v/ahttp-client?style=flat)
![PyPI - Downloads](https://img.shields.io/pypi/dm/ahttp-client?style=flat)
![PyPI - License](https://img.shields.io/pypi/l/ahttp-client?style=flat)

`ahttp-client` is a decorator-based HTTP client framework
that maps typed function parameters to HTTP requests.

### Key Features

- Declare HTTP endpoints using `@request` or the `@get`, `@post`, `@put`,
  `@patch`, `@delete`, and `@options` decoration methods.
- Use `typing.Annotated` to set HTTP parameters such as the path, query, header, or body values.
- Serialize typed request models and deserialize responses using registered codecs.
- Customize the request lifecycle using `before_hook` and `after_hook` decorators.
- Reduce boilerplate code when using HTTP client packages such as aiohttp, httpx, and requests.

## Installation

Install the extra for the HTTP client library you want to use.
Python 3.11 or later is required.

```bash
pip install "ahttp-client[aiohttp]"
pip install "ahttp-client[httpx]"
pip install "ahttp-client[requests]"
```

Include the `pydantic` extra to serialize and deserialize Pydantic models.

```bash
pip install "ahttp-client[aiohttp,pydantic]"
```

## Quick start

| Style | Supported client classes | Session class |
| --- | --- | --- |
| Async | `aiohttp.ClientSession`, `httpx.AsyncClient` | `AsyncSession` |
| Sync | `requests.Session`, `httpx.Client` | `Session` |

### Asynchronous Client
Declare a service by extending `AsyncSession`, then decorate coroutine methods
with an HTTP method and path. `Annotated` parameters determine where values are
placed in the request.

```python
import asyncio
from typing import Annotated, Any

import aiohttp

from ahttp_client import AsyncSession, Path, Response, get


class GitHubService(AsyncSession):
    def __init__(self):
        super().__init__("https://api.github.com", aiohttp.ClientSession)

    @get("/users/{user}/repos")
    async def list_repositories(
        self, response: Response, user: Annotated[str, Path]
    ) -> list[dict[str, Any]]:
        return response.json()


async def main():
    async with GitHubService() as service:
        repositories = await service.list_repositories(user="gunyu1019")
        print(repositories)


asyncio.run(main())
```

`AsyncSession` closes its underlying HTTP client when the `async with` block
ends. Decorated responses are also closed automatically after the handler
returns.

### Synchronous Client

Use `Session` and a regular function with `requests.Session` or `httpx.Client`.

```python
from typing import Annotated, Any

import requests

from ahttp_client import Path, Response, Session, get


class GitHubService(Session):
    def __init__(self):
        super().__init__("https://api.github.com", requests.Session)

    @get("/users/{user}/repos")
    def list_repositories(
        self, response: Response, user: Annotated[str, Path]
    ) -> list[dict[str, Any]]:
        return response.json()


with GitHubService() as service:
    repositories = service.list_repositories(user="gunyu1019")
    print(repositories)
```

### Request components

Use `Annotated` to describe dynamic request values.

```python
from typing import Annotated

from ahttp_client import BodyJson, Header, Path, Query, Response, post


class UserService(AsyncSession):
    @post("/users/{user_id}")
    async def update_user(
        self,
        response: Response,
        user_id: Annotated[int, Path],
        verbose: Annotated[bool, Query],
        authorization: Annotated[str, Header.custom_name("Authorization")],
        display_name: Annotated[str, BodyJson.custom_key("profile.displayName")],
    ) -> dict:
        return response.json()
```

| Component | Request location |
| --- | --- |
| `Path` | A `{placeholder}` in the path |
| `Query` | Query string |
| `Header` | Request header |
| `BodyJson` | JSON body field; supports nested keys |
| `BodyForm` | URL-encoded or multipart form field |
| `Body` | Complete raw or JSON request body |

Set `directly_response=True` on a request (or a session) when you need the
`Response` object itself instead of running the decorated handler. In that
case, close it yourself with `await response.async_close()` for async clients
or `response.close()` for sync clients.

### Model serialization

Registered codecs can convert a complete `Body` parameter before transport and
validate a direct response from its return annotation. When Pydantic is
installed, `BaseModel` types and nested model containers are supported
automatically.

Use `@serialize` and `@deserialize` to pass codec options. If the model argument
is omitted, the request body and return annotations select the codec after the
request decorator is applied.

```python
from typing import Annotated

from pydantic import BaseModel

from ahttp_client import AsyncSession, Body, post
from ahttp_client.serializer import deserialize, serialize


class CreateUser(BaseModel):
    name: str
    nickname: str | None = None


class User(BaseModel):
    id: int
    name: str


class UserService(AsyncSession):
    @post("/users", directly_response=True)
    @serialize(exclude_none=True)
    @deserialize(strict=True)
    async def create_user(
        self,
        user: Annotated[CreateUser, Body],
    ) -> User:
        ...
```

In this example, the request body is produced with
`BaseModel.model_dump(mode="json", exclude_none=True)`, and the JSON response is
validated as `User`. Because `directly_response=True` selects deserialized mode
from the registered return type, the decorated method body is not executed.
Pass a model explicitly, such as `@serialize(CreateUser)`, when it cannot be
inferred from an annotation.

### Hooks

Attach a hook to a decorated request to modify it before dispatch or transform
its result afterward. Async requests require async hooks; sync requests require
regular functions.

```python
class GitHubService(AsyncSession):
    @get("/user")
    async def current_user(self, response: Response) -> dict:
        return response.json()

    @current_user.before_hook
    async def add_authorization(self, request, path):
        request.headers["Authorization"] = "Bearer <token>"
        return request, path
```

Override `before_request()` or `after_request()` on `AsyncSession` or `Session`
to apply the same behavior to every request in a service.

## Documentation

- [English documentation](https://gunyu1019.github.io/ahttp-client/en/)
- [한국어 문서](https://gunyu1019.github.io/ahttp-client/ko/)
