Skip to content

Python API clients, thoughtfully structured.

unihttp – modern library for creating declarative API clients

Describe an endpoint once. Call it as a typed Python method. Keep your HTTP transport and response models separate.

Sync & async clients · Explicit request parameters · Pluggable serializers

An endpoint you can read.

The URL, parameters, and result type live together in a small dataclass. bind_method exposes it on your client with a typed call signature.

  1. Declare the result. User describes the response your code receives.
  2. Describe the request. Path[int] places user_id in the URL.
  3. Bind the method. get_user(user_id=1) returns a User with the configured response loader; await the call with async clients.

Choose an HTTP library in the tabs. The models stay the same; the client changes. These excerpts omit shared imports and configuration. See the complete sync or async example to run against a local demo API. aiohttp is async-only.

Endpoint & client · Python

from unihttp.clients.httpx import HTTPXSyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(HTTPXSyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.requests import RequestsSyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(RequestsSyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.aiohttp import AiohttpAsyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(AiohttpAsyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.httpx2 import HTTPX2SyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(HTTPX2SyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.niquests import NiquestsSyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(NiquestsSyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.zapros import ZaprosSyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(ZaprosSyncClient):
    get_user = bind_method(GetUser)
from unihttp.clients.urllib import UrllibSyncClient


@dataclass
class User:
    id: int
    name: str


@dataclass
class GetUser(BaseMethod[User]):
    __url__ = "/users/{user_id}"
    __method__ = "GET"

    user_id: Path[int]


class UserClient(UrllibSyncClient):
    get_user = bind_method(GetUser)

Your API contract. Your stack.

Method definitions describe the API, not the transport. Choose an HTTP client for your application and a serializer for your models.

HTTP transport

HTTPX, aiohttp, requests, niquests, zapros, or the standard-library urllib. Use a sync or async client where supported by the backend.

Compare HTTP backends →

Model serialization

Use dataclasses with Adaptix, or integrate your Pydantic and msgspec models. Configure request dumping and response loading explicitly.

Choose a serializer →

When unihttp fits

Use it for a reusable API client or SDK with explicit endpoint contracts. For a one-off request, calling your HTTP library directly may be sufficient.