Skip to main content

FastAPI-style contracts for Python application use cases.

Project description

UseCaseAPI

UseCaseAPI

FastAPI-style contracts for same-process Python application use cases.

CI PyPI Python versions License: MIT


Documentation: github.com/Wisteria30/usecaseapi/tree/main/docs

Source Code: github.com/Wisteria30/usecaseapi


UseCaseAPI gives internal application use cases stable, versioned contracts.

Define a contract with a Python Protocol, Pydantic v2 models, and domain exceptions. Bind that contract to an implementation explicitly. Call it in the same process without turning HTTP, RPC, serialization, dependency injection containers, or transaction managers into runtime requirements.

Add it when your application has internal use case nodes that need stable names, versions, input/output schemas, declared dependencies, and a canonical YAML contract catalog. 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 designed for applications with many internal use case nodes: workflow-like systems, AI-agent tool surfaces, CLIs, batch workers, FastAPI/Django backends, and codebases where accidental application-layer breaking changes are costly.

Key features

  • Code-first contracts: use Protocol, Model, UseCase, UseCaseRef, and normal Python exceptions.
  • Same-process calls: call implementations directly, with no JSON serialization in the call path.
  • Explicit bindings: connect a contract token to an implementation factory in one place.
  • Declared dependencies: expose and validate which use cases can call which other use cases.
  • Versioned identity: identify contracts as stable names such as commerce.place_order@v1.
  • Manifest artifacts: generate a YAML catalog, conservative diffs, Markdown docs, and Mermaid graphs.
  • CLI scaffolding: create a contract, implementation, and test layout from one command.
  • Typed distribution: ships py.typed for downstream type checkers.

Requirements

UseCaseAPI requires:

  • Python >=3.12,<3.15
  • Pydantic v2
  • PyYAML
  • Typer

Installation

uv add usecaseapi

Example

Create a contract

# src/commerce/usecases/place_order/v1/place_order_contract.py
from __future__ import annotations

from typing import ClassVar, Protocol

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


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


class PlaceOrderUseCaseOutput(Model):
    order_id: str


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


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


PLACE_ORDER_USECASE: UseCaseRef[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput] = define_usecase(
    PlaceOrder,
    Contract(
        name="commerce.place_order",
        version=1,
        input=PlaceOrderUseCaseInput,
        output=PlaceOrderUseCaseOutput,
        raises=(PlaceOrderError,),
    ),
)

Implement it

# src/commerce/usecases/place_order/v1/place_order_usecase.py
from commerce.usecases.place_order.v1.place_order_contract import (
    PlaceOrderUseCaseInput,
    PlaceOrderUseCaseOutput,
)


class PlaceOrderUseCase:
    async def __call__(
        self,
        input: PlaceOrderUseCaseInput,
        /,
    ) -> PlaceOrderUseCaseOutput:
        return PlaceOrderUseCaseOutput(order_id="ord_123")

The implementation class is ordinary Python. Instantiate it in your composition root or in tests with the dependencies that project actually uses.

Bind and call it

from dataclasses import dataclass

from usecaseapi import UseCaseAPI

from commerce.usecases.place_order.v1.place_order_contract import (
    PLACE_ORDER_USECASE,
    PlaceOrderUseCaseInput,
)
from commerce.usecases.place_order.v1.place_order_usecase import PlaceOrderUseCase


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


usecases = UseCaseAPI[AppContext]()
usecases.bind(PLACE_ORDER_USECASE, lambda caller: PlaceOrderUseCase())

caller = usecases.caller(AppContext(tenant_id="tenant_a"))
output = await caller.call(
    PLACE_ORDER_USECASE,
    PlaceOrderUseCaseInput(user_id="u1", sku_id="s1", quantity=1),
)

The call is same-process and direct. UseCaseAPI does not serialize the input or output.

CLI scaffolding

Create the first contract version:

usecaseapi scaffold commerce place_order --output-root src

Create the next major version from the latest existing contract:

usecaseapi scaffold commerce place_order --output-root src --next

The first command creates:

src/commerce/usecases/place_order/v1/place_order_contract.py
src/commerce/usecases/place_order/v1/place_order_usecase.py
tests/commerce/usecases/place_order/v1/test_place_order.py

See docs/scaffold.md for the generated layout and options.

Manifest

Export the canonical contract catalog:

usecaseapi manifest export composition:usecases --output usecaseapi.ucase.yaml

Validate it and check that code still matches it:

usecaseapi manifest validate usecaseapi.ucase.yaml
usecaseapi manifest check-sync composition:usecases usecaseapi.ucase.yaml

Generate Python contract and implementation skeletons from the catalog:

usecaseapi manifest scaffold usecaseapi.ucase.yaml --root .

Render derived outputs:

usecaseapi docs usecaseapi.ucase.yaml --output usecaseapi.md
usecaseapi graph usecaseapi.ucase.yaml --output usecaseapi.mmd
usecaseapi diff old.ucase.yaml new.ucase.yaml

See docs/scaffold.md, docs/manifest.md, and docs/llm-manifest-prompt.md for generated layouts, Manifest syntax, and an LLM prompt for converging requirements into usecaseapi.ucase.yaml.

Manifest docs and graph

UseCaseAPI can turn a composed application into inspectable artifacts:

  • a canonical YAML Manifest;
  • a conservative diff between Manifests;
  • Markdown documentation for use cases and dependencies;
  • Mermaid graph output for the dependency graph.

See docs/api-reference.md, docs/versioning.md, and docs/integrations.md.

What UseCaseAPI is not

UseCaseAPI is not a web framework and does not add HTTP, RPC, serialization, service locators, dependency injection containers, or transaction managers to your application runtime.

You can write plain classes and functions for small projects. UseCaseAPI becomes useful when an application has enough internal use case nodes that stable names, versions, declared dependencies, documented errors, Manifest catalogs, and generated docs start paying for themselves.

Development

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

For release validation:

uv build
uv run twine check dist/*
uv run --isolated --no-project --with dist/*.whl scripts/verify_distribution.py
uv run --isolated --no-project --with dist/*.tar.gz scripts/verify_distribution.py

See docs/pypi-publishing.md for the publishing workflow.

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.1.0.tar.gz (868.2 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.1.0-py3-none-any.whl (31.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: usecaseapi-1.1.0.tar.gz
  • Upload date:
  • Size: 868.2 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.1.0.tar.gz
Algorithm Hash digest
SHA256 df8ed2d6076a7ebc8374fcf4f9c09148ac49b1da3ace1acac56c7d4c8f9420f0
MD5 aebac141fd356ab7d063f894df280725
BLAKE2b-256 7e86c07bc745e89d803764d38368a4ef5d17900cc7aaba7172dd778d2fc89fa6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: usecaseapi-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.0 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 da5ee8e21d740e8c7862631966a60ab6a893f07843047ba85cb058b3a6312c09
MD5 db259f8f47684ff873b88065311b69c2
BLAKE2b-256 081c6f4ee5e06dc4b7e158d7e62e01445cabbf27fb7498c7ee35ac76e98e3d45

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