Skip to main content

A modern and fast library for developing API clients.

Project description

unihttp

codecov PyPI version PyPI - Python Version PyPI - Downloads GitHub License GitHub Repo stars Telegram

unihttp is a modern library for creating declarative API clients.

Table of Contents

Features

  • Declarative: Define API methods using standard Python type hints.
  • Type-Safe: Full support for static type checking.
  • Backend Agnostic: Works with httpx, aiohttp, requests and niquests.
  • 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[niquests]"  # For niquests (Sync/Async) support
# OR
pip install "unihttp[requests]" # For Requests (Sync) support
# OR
pip install "unihttp[aiohttp]"  # For Aiohttp (Async) support

Serialization Backends

unihttp allows you to choose your preferred serialization framework:

  1. Adaptix (recommended): High-performance serialization for standard Python types (dataclasses, TypedDict).
  2. Pydantic: Native support for Pydantic models.

You will need to pass the appropriate request_dumper and response_loader when initializing your client. See Powered by Adaptix or Pydantic Integration for configuration details.

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:

First, install the optional dependency:

pip install "unihttp[adaptix]"
  • 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),
)

Pydantic Integration

While unihttp works great with standard Python types and dataclasses (via adaptix), you can also natively use * Pydantic* models.

First, install the optional dependency:

pip install "unihttp[pydantic]"

Then, configure your client to use the Pydantic serializers:

from dataclasses import dataclass

from pydantic import BaseModel
from unihttp.clients.requests import RequestsSyncClient
from unihttp.markers import Body
from unihttp.method import BaseMethod
from unihttp.serializers.pydantic import PydanticDumper, PydanticLoader


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


@dataclass
class CreateUser(BaseMethod[User]):
    __url__ = "/users"
    __method__ = "POST"

    user: Body[User]


# Initialize serializers
dumper = PydanticDumper()
loader = PydanticLoader()

client = RequestsSyncClient(
    base_url="https://api.example.org",
    request_dumper=dumper,
    response_loader=loader
)

# Now standard Pydantic models are serialized/validated automatically
client.call_method(CreateUser(user=User(id=1, name="Alice")))

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

unihttp-0.2.3.tar.gz (40.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unihttp-0.2.3-py3-none-any.whl (28.3 kB view details)

Uploaded Python 3

File details

Details for the file unihttp-0.2.3.tar.gz.

File metadata

  • Download URL: unihttp-0.2.3.tar.gz
  • Upload date:
  • Size: 40.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for unihttp-0.2.3.tar.gz
Algorithm Hash digest
SHA256 9f1331251009c0c8c624ad43a51e254ad60e98a3d27ab090bb1122f39c39a72f
MD5 cee267278a555485c00c2ddb79bac513
BLAKE2b-256 6cbee56ab7c2c96f44937c6c47c60c1af923739e943c628b58e6ac3c2968da54

See more details on using hashes here.

File details

Details for the file unihttp-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: unihttp-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 28.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for unihttp-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 19fdb2fc8ef455f04cc21d34ca5cdd6faf24feb4350c664492b2ccff8266ed35
MD5 a751966171a37d9b5f104a1b0b8fc307
BLAKE2b-256 6e69dbb4e3f278f62dd67934d056c187d7be776c1e8519560fe411eb568845e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page