Skip to main content

FastAPI-style contracts for Python application use cases.

Project description

UseCaseAPI

FastAPI-style contracts for Python application use cases.

UseCaseAPI brings code-first contract clarity to same-process Python use cases. Define contracts with Python Protocols, Pydantic models, and domain exceptions, then bind them to implementations without making HTTP the boundary.

Add it when your application has internal use case nodes that need stable names, versions, input/output schemas, declared dependencies, and contract snapshots. It helps teams and AI coding agents see what can be called, what can fail, and whether a change broke an application-layer contract before that break ships.

It is inspired by the developer experience of FastAPI, but it is not a web framework. FastAPI turns Python type hints into a public HTTP API contract. UseCaseAPI turns Python type definitions into public contracts for your application layer, without forcing HTTP, RPC, serialization, or a dependency-injection container into the hot path.

UseCaseAPI is designed for applications with many internal use case nodes: workflow-like systems, ROS-like node composition, AI-agent tool surfaces, batch workers, CLIs, FastAPI/Django backends, and systems where AI-assisted coding makes accidental breaking changes easier than before.

What it gives you

  • Code-first usecase contracts using Protocol, Model, and normal Python exceptions.
  • Same-process direct calls with no JSON serialization in the hot path.
  • Explicit binding from contract token to implementation factory.
  • Declared usecase dependency graph with strict undeclared-call protection.
  • Versioned contract identity: name@vN.
  • Contract snapshots, conservative diffing, Markdown docs, Mermaid graph export, and CLI scaffolding.
  • No DI container, no transaction manager, no HTTP/RPC runtime.

Install

uv add usecaseapi

For local development:

uv sync --extra dev
uv run pytest
uv run mypy
uv run ruff check .

Python support: 3.12, 3.13, and 3.14. Pydantic v2 is required.

Quick example

Define a contract:

from __future__ import annotations

from typing import ClassVar, Literal, Protocol

from usecaseapi import Contract, Model, UseCase, UseCaseError, UseCaseRef, define_usecase


class Input(Model):
    user_id: str
    sku_id: str
    quantity: int


class Output(Model):
    order_id: str
    status: Literal["accepted"]


class PlaceOrderError(UseCaseError):
    code: ClassVar[str] = "orders.place_order"


class InventoryShortage(PlaceOrderError):
    code: ClassVar[str] = "orders.place_order.inventory_shortage"

    def __init__(self, *, sku_id: str, requested: int, available: int) -> None:
        self.sku_id = sku_id
        self.requested = requested
        self.available = available
        super().__init__(
            f"inventory shortage: sku_id={sku_id}, requested={requested}, available={available}"
        )


class PlaceOrder(UseCase[Input, Output], Protocol):
    async def __call__(self, input: Input, /) -> Output:
        ...


PLACE_ORDER: UseCaseRef[Input, Output] = define_usecase(
    PlaceOrder,
    Contract(
        name="orders.place_order",
        version=1,
        input=Input,
        output=Output,
        raises=(PlaceOrderError,),
        known_errors=(InventoryShortage,),
    ),
)

Implement it:

from app.contracts.orders.place_order.v1 import Input, Output, PlaceOrder


class PlaceOrderImpl:
    async def __call__(self, input: Input, /) -> Output:
        return Output(order_id="ord_123", status="accepted")


_impl: PlaceOrder = PlaceOrderImpl()

Bind and call it:

from dataclasses import dataclass

from usecaseapi import UseCaseAPI


@dataclass(frozen=True)
class AppContext:
    tenant_id: str


usecases = UseCaseAPI[AppContext]()
usecases.bind(PLACE_ORDER, lambda caller: PlaceOrderImpl())

output = await usecases.caller(AppContext(tenant_id="t1")).call(PLACE_ORDER, input)

Scaffold

usecaseapi scaffold orders.place_order --version 1

# Create the next major contract version by scanning app/contracts/orders/place_order/v*.py
usecaseapi scaffold orders.place_order --next

This creates:

app/contracts/orders/place_order/v1.py
app/usecases/orders/place_order.py
tests/test_orders_place_order_v1.py

Why not just write classes yourself?

You can, and for small projects you should. UseCaseAPI becomes useful when you have many internal nodes and you need to know:

  • which functions are public application-layer contracts;
  • which version each caller depends on;
  • which usecase dependencies are allowed;
  • which domain exceptions are part of the contract;
  • whether AI-assisted changes silently broke a stable contract;
  • what the application graph looks like.

UseCaseAPI is a small runtime plus guardrails for that problem.

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

usecaseapi-1.0.0.tar.gz (34.7 kB view details)

Uploaded Source

Built Distribution

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

usecaseapi-1.0.0-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file usecaseapi-1.0.0.tar.gz.

File metadata

  • Download URL: usecaseapi-1.0.0.tar.gz
  • Upload date:
  • Size: 34.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for usecaseapi-1.0.0.tar.gz
Algorithm Hash digest
SHA256 2c5b595554b7265070cf0e6ce466a608b72597ed4f885ec076a7431dc08ae37c
MD5 ab44de631869c7430b8ad2558ba3c469
BLAKE2b-256 14d7e1200a699e2f03be1a531aa4008957efd293862ad9c715fcf19d953932a6

See more details on using hashes here.

File details

Details for the file usecaseapi-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: usecaseapi-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 19.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for usecaseapi-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae13772a49d1e85e05ae84aae00019ab293b4da84f0ca42dde97f1b4c08e5c02
MD5 c4d9693a17573a4a314e9c97c1472a71
BLAKE2b-256 ebdf44357e6e1a5b86713b580d6a0c0cec63711d4cc769f2f518deb02a30e223

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