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.2.tar.gz (131.6 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.2-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: clientcraft-0.1.2.tar.gz
  • Upload date:
  • Size: 131.6 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.2.tar.gz
Algorithm Hash digest
SHA256 60801781195f0206bc05373c9b807e419626b7eeaadfdd0bcfd4b5d39dcddaf4
MD5 5a6890355a4900a50d507e3b5e21e39c
BLAKE2b-256 88920dc8ad80f6e85548d51a8ef053ca78bfe2753538bdf2e406a267b3d93fc3

See more details on using hashes here.

Provenance

The following attestation bundles were made for clientcraft-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: clientcraft-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 24.2 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ad4f1a211ab8650f4cf54c716eed5a6e629f83821c770a5264a51e7f2e6638ac
MD5 c26722a29fedcfa38e28349bbd99c38e
BLAKE2b-256 bc2099733abb9f1983567de51bcb32b419437c70629e8526894cbac9d676bdf7

See more details on using hashes here.

Provenance

The following attestation bundles were made for clientcraft-0.1.2-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