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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c5b595554b7265070cf0e6ce466a608b72597ed4f885ec076a7431dc08ae37c
|
|
| MD5 |
ab44de631869c7430b8ad2558ba3c469
|
|
| BLAKE2b-256 |
14d7e1200a699e2f03be1a531aa4008957efd293862ad9c715fcf19d953932a6
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae13772a49d1e85e05ae84aae00019ab293b4da84f0ca42dde97f1b4c08e5c02
|
|
| MD5 |
c4d9693a17573a4a314e9c97c1472a71
|
|
| BLAKE2b-256 |
ebdf44357e6e1a5b86713b580d6a0c0cec63711d4cc769f2f518deb02a30e223
|