Skip to content

API Reference

Auto-generated from docstrings. Work in progress

Clients

clientcraft.client.APIClient

Bases: BaseAPIClient[HttpBackend]

Base class for declarative synchronous API clients.

Subclass this and declare endpoints using type annotations:

class UserAPI(APIClient):
    get_user: Get[GetUserRequest, User, Literal["/users/{user_id}"]]
    create_user: Post[CreateUserRequest, User, Literal["/users"]]
    delete_user: Delete[DeleteUserRequest, None, Literal["/users/{user_id}"]]

client = UserAPI(base_url="https://api.example.com", backend=backend)
user = client.get_user(GetUserRequest(user_id="123"))
Source code in src/clientcraft/client.py
class APIClient(BaseAPIClient[HttpBackend]):
    """
    Base class for declarative synchronous API clients.

    Subclass this and declare endpoints using type annotations:

        class UserAPI(APIClient):
            get_user: Get[GetUserRequest, User, Literal["/users/{user_id}"]]
            create_user: Post[CreateUserRequest, User, Literal["/users"]]
            delete_user: Delete[DeleteUserRequest, None, Literal["/users/{user_id}"]]

        client = UserAPI(base_url="https://api.example.com", backend=backend)
        user = client.get_user(GetUserRequest(user_id="123"))
    """

    _descriptor_factory = _SyncEndpointDescriptor

clientcraft.async_client.AsyncAPIClient

Bases: BaseAPIClient[AsyncHttpBackend]

Base class for declarative asynchronous API clients.

Subclass this and declare endpoints using type annotations:

class UserAPI(AsyncAPIClient):
    get_user: AsyncGet[GetUserRequest, User, Literal["/users/{user_id}"]]
    create_user: AsyncPost[CreateUserRequest, User, Literal["/users"]]
    delete_user: AsyncDelete[DeleteUserRequest, None, Literal["/users/{user_id}"]]

client = UserAPI(base_url="https://api.example.com", backend=backend)
user = await client.get_user(GetUserRequest(user_id="123"))
Source code in src/clientcraft/async_client.py
class AsyncAPIClient(BaseAPIClient[AsyncHttpBackend]):
    """
    Base class for declarative asynchronous API clients.

    Subclass this and declare endpoints using type annotations:

        class UserAPI(AsyncAPIClient):
            get_user: AsyncGet[GetUserRequest, User, Literal["/users/{user_id}"]]
            create_user: AsyncPost[CreateUserRequest, User, Literal["/users"]]
            delete_user: AsyncDelete[DeleteUserRequest, None, Literal["/users/{user_id}"]]

        client = UserAPI(base_url="https://api.example.com", backend=backend)
        user = await client.get_user(GetUserRequest(user_id="123"))
    """

    _descriptor_factory = _AsyncEndpointDescriptor

Endpoint types

The endpoint types (Get, Post, Put, Patch, Delete and their Async* counterparts) are constructed at runtime via a metaclass and described statically by the bundled type stubs. See How It Works for the mechanism and Endpoints for usage.

Response wrappers

clientcraft.TextResponse

Bases: BaseModel

Wrapper for text responses.

Usage

get_health: Get[None, TextResponse, Literal["/health"]]

Source code in src/clientcraft/_responses.py
class TextResponse(BaseModel):
    """
    Wrapper for text responses.

    Usage:
        get_health: Get[None, TextResponse, Literal["/health"]]
    """

    content: str

clientcraft.BytesResponse

Bases: BaseModel

Wrapper for binary responses.

Usage

download_file: Get[FileRequest, BytesResponse, Literal["/files/{id}"]]

Source code in src/clientcraft/_responses.py
class BytesResponse(BaseModel):
    """
    Wrapper for binary responses.

    Usage:
        download_file: Get[FileRequest, BytesResponse, Literal["/files/{id}"]]
    """

    content: bytes

Errors

clientcraft.HttpError

Bases: Exception

HTTP error with status code, response content, and the failed endpoint.

Source code in src/clientcraft/_base.py
class HttpError(Exception):
    """HTTP error with status code, response content, and the failed endpoint."""

    def __init__(
        self,
        status_code: int,
        content: bytes,
        headers: dict[str, str] | None = None,
        endpoint_info: EndpointInfo | None = None,
    ) -> None:
        self.status_code = status_code
        self.content = content
        self.headers = headers or {}
        self.endpoint_info = endpoint_info
        super().__init__(f"HTTP {status_code}: {content.decode('utf-8', errors='replace')}")

Core types

clientcraft.RequestStyle

Bases: Enum

How to serialize request data for the HTTP call.

Source code in src/clientcraft/_types.py
class RequestStyle(Enum):
    """How to serialize request data for the HTTP call."""

    QUERY = auto()  # Serialize to query string (GET, DELETE)
    BODY = auto()  # Serialize to JSON body (POST, PUT, PATCH)

clientcraft.ResponseStyle

Bases: Enum

How to deserialize response data.

Source code in src/clientcraft/_types.py
class ResponseStyle(Enum):
    """How to deserialize response data."""

    JSON = auto()  # Parse as JSON into Pydantic model (default)
    TEXT = auto()  # Return as string
    BYTES = auto()  # Return raw bytes
    NONE = auto()  # No response body expected (204 No Content, etc.)

clientcraft.EndpointInfo dataclass

Metadata about an endpoint, stored in Annotated type.

Source code in src/clientcraft/_types.py
@dataclass(frozen=True)
class EndpointInfo:
    """Metadata about an endpoint, stored in Annotated type."""

    method: HTTPMethod
    path: str
    request_style: RequestStyle
    response_style: ResponseStyle

clientcraft.ExtractedEndpoint dataclass

Result of extracting endpoint info from a type hint.

Source code in src/clientcraft/_types.py
@dataclass(frozen=True)
class ExtractedEndpoint:
    """Result of extracting endpoint info from a type hint."""

    request_type: type[BaseModel] | None
    response_type: type[BaseModel] | None
    info: EndpointInfo
    error_map: dict[StatusKey, type[DomainError]] = field(default_factory=dict)

Backend protocols

clientcraft.backends.HttpBackend

Bases: Protocol

Protocol for synchronous HTTP backends.

Any object with a matching request method works - no inheritance required. This allows easy integration with requests, httpx, or custom implementations.

Example with requests

class RequestsBackend: def init(self, session: requests.Session | None = None): self.session = session or requests.Session()

def request(self, method, url, *, content=None, headers=None, timeout=None):
    resp = self.session.request(method, url, data=content, headers=headers, timeout=timeout)
    return resp  # requests.Response satisfies HttpResponse protocol
Source code in src/clientcraft/backends/_protocols.py
class HttpBackend(Protocol):
    """
    Protocol for synchronous HTTP backends.

    Any object with a matching `request` method works - no inheritance required.
    This allows easy integration with requests, httpx, or custom implementations.

    Example with requests:
        class RequestsBackend:
            def __init__(self, session: requests.Session | None = None):
                self.session = session or requests.Session()

            def request(self, method, url, *, content=None, headers=None, timeout=None):
                resp = self.session.request(method, url, data=content, headers=headers, timeout=timeout)
                return resp  # requests.Response satisfies HttpResponse protocol
    """

    def request(
        self,
        method: str,
        url: str,
        *,
        content: bytes | None = None,
        headers: dict[str, str] | None = None,
        timeout: float | None = None,
    ) -> HttpResponse:
        """
        Make a synchronous HTTP request.

        Args:
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            url: Full URL to request
            content: Optional request body as bytes
            headers: Optional request headers
            timeout: Optional timeout in seconds

        Returns:
            An object satisfying the HttpResponse protocol
        """
        ...

request

request(
    method: str,
    url: str,
    *,
    content: bytes | None = None,
    headers: dict[str, str] | None = None,
    timeout: float | None = None,
) -> HttpResponse

Make a synchronous HTTP request.

Parameters:

Name Type Description Default
method str

HTTP method (GET, POST, PUT, DELETE, etc.)

required
url str

Full URL to request

required
content bytes | None

Optional request body as bytes

None
headers dict[str, str] | None

Optional request headers

None
timeout float | None

Optional timeout in seconds

None

Returns:

Type Description
HttpResponse

An object satisfying the HttpResponse protocol

Source code in src/clientcraft/backends/_protocols.py
def request(
    self,
    method: str,
    url: str,
    *,
    content: bytes | None = None,
    headers: dict[str, str] | None = None,
    timeout: float | None = None,
) -> HttpResponse:
    """
    Make a synchronous HTTP request.

    Args:
        method: HTTP method (GET, POST, PUT, DELETE, etc.)
        url: Full URL to request
        content: Optional request body as bytes
        headers: Optional request headers
        timeout: Optional timeout in seconds

    Returns:
        An object satisfying the HttpResponse protocol
    """
    ...

clientcraft.backends.AsyncHttpBackend

Bases: Protocol

Protocol for asynchronous HTTP backends.

Any object with a matching async request method works - no inheritance required. This allows easy integration with httpx.AsyncClient, aiohttp, or custom implementations.

Example with httpx

class HttpxBackend: def init(self, client: httpx.AsyncClient | None = None): self.client = client or httpx.AsyncClient()

async def request(self, method, url, *, content=None, headers=None, timeout=None):
    resp = await self.client.request(method, url, content=content, headers=headers, timeout=timeout)
    return resp  # httpx.Response satisfies HttpResponse protocol
Source code in src/clientcraft/backends/_protocols.py
class AsyncHttpBackend(Protocol):
    """
    Protocol for asynchronous HTTP backends.

    Any object with a matching async `request` method works - no inheritance required.
    This allows easy integration with httpx.AsyncClient, aiohttp, or custom implementations.

    Example with httpx:
        class HttpxBackend:
            def __init__(self, client: httpx.AsyncClient | None = None):
                self.client = client or httpx.AsyncClient()

            async def request(self, method, url, *, content=None, headers=None, timeout=None):
                resp = await self.client.request(method, url, content=content, headers=headers, timeout=timeout)
                return resp  # httpx.Response satisfies HttpResponse protocol
    """

    async def request(
        self,
        method: str,
        url: str,
        *,
        content: bytes | None = None,
        headers: dict[str, str] | None = None,
        timeout: float | None = None,
    ) -> HttpResponse:
        """
        Make an asynchronous HTTP request.

        Args:
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            url: Full URL to request
            content: Optional request body as bytes
            headers: Optional request headers
            timeout: Optional timeout in seconds

        Returns:
            An object satisfying the HttpResponse protocol
        """
        ...

request async

request(
    method: str,
    url: str,
    *,
    content: bytes | None = None,
    headers: dict[str, str] | None = None,
    timeout: float | None = None,
) -> HttpResponse

Make an asynchronous HTTP request.

Parameters:

Name Type Description Default
method str

HTTP method (GET, POST, PUT, DELETE, etc.)

required
url str

Full URL to request

required
content bytes | None

Optional request body as bytes

None
headers dict[str, str] | None

Optional request headers

None
timeout float | None

Optional timeout in seconds

None

Returns:

Type Description
HttpResponse

An object satisfying the HttpResponse protocol

Source code in src/clientcraft/backends/_protocols.py
async def request(
    self,
    method: str,
    url: str,
    *,
    content: bytes | None = None,
    headers: dict[str, str] | None = None,
    timeout: float | None = None,
) -> HttpResponse:
    """
    Make an asynchronous HTTP request.

    Args:
        method: HTTP method (GET, POST, PUT, DELETE, etc.)
        url: Full URL to request
        content: Optional request body as bytes
        headers: Optional request headers
        timeout: Optional timeout in seconds

    Returns:
        An object satisfying the HttpResponse protocol
    """
    ...

clientcraft.backends.HttpResponse

Bases: Protocol

Protocol for HTTP responses.

Any object with these properties works - no inheritance required. Most HTTP libraries (requests, httpx, aiohttp) return responses that satisfy this protocol or can be easily adapted.

Source code in src/clientcraft/backends/_protocols.py
class HttpResponse(Protocol):
    """
    Protocol for HTTP responses.

    Any object with these properties works - no inheritance required.
    Most HTTP libraries (requests, httpx, aiohttp) return responses that
    satisfy this protocol or can be easily adapted.
    """

    @property
    def status_code(self) -> int:
        """HTTP status code (e.g., 200, 404, 500)."""
        ...

    @property
    def content(self) -> bytes:
        """Raw response body as bytes."""
        ...

    @property
    def headers(self) -> dict[str, str]:
        """Response headers as a dictionary."""
        ...

status_code property

status_code: int

HTTP status code (e.g., 200, 404, 500).

content property

content: bytes

Raw response body as bytes.

headers property

headers: dict[str, str]

Response headers as a dictionary.