A modern and fast library for developing API clients.
Project description
unihttp
unihttp is a modern and fast library for creating declarative API clients.
Table of Contents
- Features
- Installation
- Quick Start
- Markers Reference
- Middleware
- Error Handling
- Custom JSON Serialization
- Powered by Adaptix
Features
- Declarative: Define API methods using standard Python type hints.
- Type-Safe: Full support for static type checking.
- Backend Agnostic: Works with
httpx,aiohttp, andrequests. - Extensible: Powerful middleware and error handling systems.
Installation
pip install unihttp
To include a specific HTTP backend (recommended):
pip install "unihttp[httpx]" # For HTTPX (Sync/Async) support
# OR
pip install "unihttp[requests]" # For Requests (Sync) support
# OR
pip install "unihttp[aiohttp]" # For Aiohttp (Async) support
Quick Start
1. Define Methods
unihttp uses markers to map method arguments to HTTP request components.
from dataclasses import dataclass
from unihttp import BaseMethod, Path, Query, Body, Header, Form, File
@dataclass
class User:
id: int
name: str
email: str
@dataclass
class GetUser(BaseMethod[User]):
__url__ = "/users/{id}"
__method__ = "GET"
id: Path[int]
compact: Query[bool] = False
@dataclass
class CreateUser(BaseMethod[User]):
__url__ = "/users"
__method__ = "POST"
name: Body[str]
email: Body[str]
2. Client Implementation Strategies
You can choose between a purely declarative style using bind_method or a more imperative style using call_method.
Option A: Declarative Client (via bind_method)
This is the most concise way to define your client. You simply bind the methods to the client class.
[!NOTE] PyCharm Users: There is currently a known issue with displaying type hints for descriptors like
bind_method(see PY-51768). This is expected to be fixed in the 2026.1 version.
from unihttp import bind_method
from unihttp.clients.httpx import HTTPXSyncClient
from unihttp.serializers.adaptix import DEFAULT_RETORT
class UserClient(HTTPXSyncClient):
get_user = bind_method(GetUser)
create_user = bind_method(CreateUser)
client = UserClient(
base_url="https://api.example.com",
request_dumper=DEFAULT_RETORT,
response_loader=DEFAULT_RETORT
)
user = client.get_user(id=123)
Option B: Imperative Client (via call_method)
If you need more control, need to preprocess arguments, or simply prefer explicit method definitions, you can define methods in the client and use call_method.
class UserClient(HTTPXSyncClient):
def get_user(self, user_id: int) -> User:
# You can add custom logic here before the call
return self.call_method(GetUser(id=user_id))
def create_user(self, name: str, email: str) -> User:
return self.call_method(CreateUser(name=name, email=email))
Markers Reference
unihttp provides several markers to define how arguments are serialized:
Path: Substitutes placeholders in the__url__(e.g.,/users/{id}).Query: Adds parameters to the URL query string.Body: Sends data as the JSON request body.Header: Adds HTTP headers to the request.Form: Sends data as form-encoded (application/x-www-form-urlencoded).File: Used for multipart file uploads.UploadFile: A wrapper for file uploads that allows specifying a filename and content type (e.g.,UploadFile(b"content", filename="test.txt")).
Middleware
Middleware allows you to intercept requests and responses globally. This is useful for logging, authentication, or modifying requests on the fly.
from unihttp.middlewares.base import Middleware
from unihttp.http.request import HTTPRequest
from unihttp.http.response import HTTPResponse
class LoggingMiddleware(Middleware):
def handle(self, request: HTTPRequest, next_handler) -> HTTPResponse:
print(f"Requesting {request.url}")
# Call the next handler in the chain
response = next_handler(request)
print(f"Status: {response.status_code}")
return response
client = HTTPXSyncClient(
# ...
middleware=[LoggingMiddleware()]
)
Error Handling
unihttp offers a layered approach to error handling, giving you control at multiple levels.
1. Method-Level Handling
Override on_error in your Method class to handle specific status codes for that endpoint.
@dataclass
class GetUser(BaseMethod[User]):
# ...
def on_error(self, response):
if response.status_code == 404:
return None # Return None (or a default object) instead of raising
return super().on_error(response)
2. Client-Level Handling
Override handle_error in your Client class to catch errors that weren't handled by the method. This is great for global concerns like token expiration.
class MyClient(HTTPXSyncClient):
def handle_error(self, response: HTTPResponse, method):
if response.status_code == 401:
raise MyAuthException("Session expired, please log in again.")
3. Middleware-Level Handling
You can wrap the execution in a try/except block or inspect the response within a middleware. This is useful for logging exceptions or global error reporting.
class ErrorReportingMiddleware(Middleware):
def handle(self, request: HTTPRequest, next_handler):
try:
return next_handler(request)
except Exception as e:
# Report exception to external service
sentry_sdk.capture_exception(e)
raise
4. Response Body Validation
Sometimes APIs return 200 OK but the body contains an error message. You can override validate_response to handle this.
# In your Method or Client
def validate_response(self, response: HTTPResponse):
if "error" in response.data:
raise ApiError(response.data["error"])
Custom JSON Serialization
You can use high-performance JSON libraries like orjson or ujson by passing custom json_dumps and json_loads to the client.
import orjson
from unihttp.clients.httpx import HTTPXSyncClient
client = HTTPXSyncClient(
# ...
json_dumps=lambda x: orjson.dumps(x).decode(),
json_loads=orjson.loads
)
Powered by Adaptix
unihttp leverages adaptix for all data serialization and validation tasks. adaptix is a powerful and extremely fast library that allows you to:
- Validate data strictly against your type hints.
- Serialize/Deserialize complex data structures (dataclasses, TypedDicts, etc.) with high performance.
- Customize serialization logic (field renaming, value transformation) using
Retort.
Crucially, you can customize serialization down to individual fields in each method, giving you granular control over how your data is processed.
from adaptix import Retort, name_mapping, P
from unihttp.serializers.adaptix import AdaptixDumper, AdaptixLoader, DEFAULT_RETORT
# Create a Retort that renames specific fields (e.g., camelCase for external API)
retort = Retort(
recipe=[
name_mapping(map={"user_name": "userName"}),
dumper(P[CreateUser].email, lambda x: x.lower()),
]
)
retort.extend(DEFAULT_RETORT)
client = UserClient(
# ...
request_dumper=AdaptixDumper(retort),
response_loader=AdaptixLoader(retort),
)
Project details
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 unihttp-0.2.0.tar.gz.
File metadata
- Download URL: unihttp-0.2.0.tar.gz
- Upload date:
- Size: 32.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5570b12b80bcc7953c7aed51da1e124d882399b63a3e1f753d0386df9fb55516
|
|
| MD5 |
205f8cc5d438f6096f05c87f3b68d0e4
|
|
| BLAKE2b-256 |
3449b7ff4c5bcb9c13588cb1d9bd7e68632edf69ebe0d5d55118543f96e41e77
|
File details
Details for the file unihttp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: unihttp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40921ce7141a04d3bc3f508f8a2c1ec25f27812c80ef9314f5cdb48dde0aa9ae
|
|
| MD5 |
5ce38601cb320f61b98357015e2b280d
|
|
| BLAKE2b-256 |
9db954cbff21534da6523cdc78c0eaf2f498abf250e7e35aef92e4c6c8cb177a
|