Skip to main content

A declarative, type-safe API client framework for Python

Project description

clientcraft

A declarative, type-safe API client framework for Python 3.12+.

Define your HTTP API as a class of typed annotations and get fully-typed, Pydantic-validated request/response handling for free — no boilerplate per endpoint.

📖 Full documentation: https://sunbright-technologies.github.io/clientcraft/

Features

  • Declarative endpoint definitions using type annotations
  • Type-safe request and response handling with Pydantic
  • Sync and async client support
  • Pluggable backends: urllib (no deps), requests, httpx, aiohttp, or custom
  • Multiple response types: JSON, text, bytes, or no content
  • Parameterless endpoints: declare Get[None, ...] for endpoints that take no request

Installation

# Using uv (urllib backend works with no extra dependencies)
uv add clientcraft

# With a specific backend
uv add "clientcraft[requests]"
uv add "clientcraft[httpx]"
uv add "clientcraft[aiohttp]"

# With all optional backends
uv add "clientcraft[all]"

Quick Start

Define your API

from typing import Literal
from pydantic import BaseModel
from clientcraft import Get, Post, Delete
from clientcraft.client import APIClient

# Define request/response models
class GetUserRequest(BaseModel):
    user_id: str

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

class CreateUserRequest(BaseModel):
    name: str
    email: str

class DeleteUserRequest(BaseModel):
    user_id: str

# Define your API client declaratively
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}"]]

Use your API

Concrete backends live in their own submodules and must be imported from there (clientcraft.backends itself only exposes the protocols):

from clientcraft.backends.requests import RequestsBackend

# Create client with a backend
backend = RequestsBackend()
client = UserAPI(base_url="https://api.example.com", backend=backend)

# Make requests - fully typed!
user = client.get_user(GetUserRequest(user_id="123"))
print(user.name)  # Type checker knows this is a User

# Create a user
new_user = client.create_user(CreateUserRequest(name="Alice", email="alice@example.com"))

The standard-library UrllibBackend requires no third-party dependencies:

from clientcraft.backends.urllib import UrllibBackend

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

Async Usage

from typing import Literal
from clientcraft import AsyncGet, AsyncPost
from clientcraft.async_client import AsyncAPIClient
from clientcraft.backends.aiohttp import AiohttpBackend

class AsyncUserAPI(AsyncAPIClient):
    get_user: AsyncGet[GetUserRequest, User, Literal["/users/{user_id}"]]
    create_user: AsyncPost[CreateUserRequest, User, Literal["/users"]]

async with AiohttpBackend() as backend:
    client = AsyncUserAPI(base_url="https://api.example.com", backend=backend)
    user = await client.get_user(GetUserRequest(user_id="123"))

Endpoint Types

Sync Async HTTP Method Request Style Description
Get AsyncGet GET Query params Read operations
Post AsyncPost POST JSON body Create operations
Put AsyncPut PUT JSON body Full update
Patch AsyncPatch PATCH JSON body Partial update
Delete AsyncDelete DELETE Query params Delete operations

Each is parameterized as Endpoint[RequestModel, ResponseModel, Literal["/path"]]. Path parameters ({user_id}) are pulled from the request model; remaining fields become the query string (GET/DELETE) or JSON body (POST/PUT/PATCH).

Parameterless Endpoints

For endpoints that take no parameters at all, declare the request type as None. The endpoint can then be called with no argument (or an explicit None):

from typing import Literal
from clientcraft import Get
from clientcraft.client import APIClient

class StatusAPI(APIClient):
    list_users: Get[None, UserList, Literal["/users"]]

client = StatusAPI(base_url="https://api.example.com", backend=backend)

users = client.list_users()       # no argument
users = client.list_users(None)   # explicit None — equivalent

Response Types

from typing import Literal
from clientcraft import Get, Delete, TextResponse, BytesResponse

class FilesAPI(APIClient):
    # JSON response (default) — parsed into the Pydantic model
    get_user: Get[GetUserRequest, User, Literal["/users/{user_id}"]]

    # Text response — returned as TextResponse(content=...)
    health_check: Get[None, TextResponse, Literal["/health"]]

    # Binary response — returned as BytesResponse(content=...)
    download: Get[GetFileRequest, BytesResponse, Literal["/files/{file_id}"]]

    # No response body (e.g. 204 No Content) — returns None
    delete_user: Delete[DeleteUserRequest, None, Literal["/users/{user_id}"]]

Backends

Backend Module Sync/Async Extra
UrllibBackend clientcraft.backends.urllib Sync none (stdlib)
RequestsBackend clientcraft.backends.requests Sync requests
HttpxBackend clientcraft.backends.httpx Sync httpx
HttpxAsyncBackend clientcraft.backends.httpx Async httpx
AiohttpBackend clientcraft.backends.aiohttp Async aiohttp

Backends are protocol-based — any object implementing the HttpBackend / AsyncHttpBackend protocol works, so you can supply your own.

Development

# Clone and setup
cd clientcraft
uv sync --all-extras

# Run tests
uv run pytest

# Type checking
uv run mypy src tests

# Linting and formatting
uv run ruff check src tests
uv run ruff format --check src tests

License

MIT

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

clientcraft-0.1.1.tar.gz (127.7 kB view details)

Uploaded Source

Built Distribution

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

clientcraft-0.1.1-py3-none-any.whl (24.0 kB view details)

Uploaded Python 3

File details

Details for the file clientcraft-0.1.1.tar.gz.

File metadata

  • Download URL: clientcraft-0.1.1.tar.gz
  • Upload date:
  • Size: 127.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clientcraft-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3c434ff1e8827ab9f039857c3ed7011dbdc373cb51bcb6d86bf4bcc7417f87a5
MD5 51c110a32afa7147af0277483aae07b2
BLAKE2b-256 c2dbb507f9819960b771698c78f1cd47fc462b27d8e0909e93511fdd14b9313e

See more details on using hashes here.

Provenance

The following attestation bundles were made for clientcraft-0.1.1.tar.gz:

Publisher: publish.yml on SunBright-Technologies/clientcraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clientcraft-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: clientcraft-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 24.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clientcraft-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8ade191cc20e9a883eb048385032d42459bc7c4b6f3ee777b899ad62376adb4b
MD5 dd5f09de345b4ebb4981530d2b7f40e2
BLAKE2b-256 d7183a735275cb01c5a88379f3b44a521dffead18346ad844d4cb9e22fdd5f4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for clientcraft-0.1.1-py3-none-any.whl:

Publisher: publish.yml on SunBright-Technologies/clientcraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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