This release is a pre-release and may not be stable for production use.
UseCaseCore
The standard runtime for application use cases.
UseCaseCore gives business actions one explicit, typed, transactional execution model across validation, state loading, policy checks, transitions, audit, idempotency, events, and side effects.
Why
Your API layer is standardized. Your data layer is standardized. Your service layer is still where business logic leaks into routes, model methods, jobs, and helpers.
UseCaseCore standardizes that missing layer without replacing FastAPI, SQLModel, SQLAlchemy, Postgres, Alembic, Oso, pytransitions, Temporal, or the stack you already use.
FastAPI standardizes the API layer. SQLAlchemy standardizes persistence. UseCaseCore standardizes the business action boundary.
The command layer Python apps keep rebuilding by accident. A standard execution spine for Python backend mutations.
Install
pip install usecasecore==0.1.0a3
Core path
command
-> validate
-> check idempotency
-> load state
-> check policy
-> check transitions
-> open transaction
-> apply changes
-> write audit
-> emit events
-> queue side effects
-> return result
Before / After: the route handler problem
Stop hiding business mutations in FastAPI routes.
Without a use-case boundary, a route handler for something like
MoveInventory tends to collect every operational concern in one place:
- request validation
- business validation
- database session access
- row loading and locking
- permission checks
- state transition checks
- balance mutation
- movement history
- audit logging
- event publishing
- job enqueueing
- idempotency handling
- response shaping
@app.post("/inventory/move")
def move_inventory(request: MoveInventoryRequest, session: Session = Depends(get_session)):
# validate request
# load source and destination balances
# check permissions
# check inventory invariants
# update balances
# create movement history
# write audit
# publish event
# enqueue low-stock job
# remember idempotency
# return response
With UseCaseCore, the route becomes transport glue:
@app.post("/inventory/move")
def move_inventory(request: MoveInventoryRequest):
command = request.to_command()
result = move_inventory_use_case.execute(command)
return MoveInventoryResponse.from_result(result)
The use case owns the authoritative business mutation boundary:
validate -> idempotency -> load state -> policy -> transitions -> transaction -> apply -> audit -> events -> jobs -> result
Real backend example
The FastAPI + SQLAlchemy inventory example
shows the same MoveInventory action inside a realistic Python backend stack:
FastAPI route
-> Pydantic request
-> MoveInventoryCommand
-> MoveInventoryUseCase
-> SQLAlchemy repository
-> transaction
-> inventory_movements + audit_records + outbox_records + idempotency_records
-> Pydantic response
The FastAPI route is transport glue. The use case is the business mutation boundary. SQLAlchemy owns persistence. UseCaseCore owns the action lifecycle:
validate -> idempotency -> load state -> policy -> transitions -> transaction -> apply -> audit -> outbox -> jobs -> result
Quick example
from examples.move_inventory import (
InMemoryInventoryRepository,
MoveInventoryCommand,
MoveInventoryUseCase,
)
from usecasecore import InMemoryIdempotencyStore
repo = InMemoryInventoryRepository()
repo.set_balance(product_id="sku-1", bin_id="A", qty=10)
repo.set_balance(product_id="sku-1", bin_id="B", qty=1)
use_case = MoveInventoryUseCase(
repository=repo,
idempotency_store=InMemoryIdempotencyStore(),
)
result = use_case.execute(
MoveInventoryCommand(
request_id="req-1",
idempotency_key="move-sku-1-A-B-4",
product_id="sku-1",
from_bin_id="A",
to_bin_id="B",
qty=4,
moved_by_user_id="user-1",
reason="rebalancing",
)
)
assert result.success is True
assert result.source_qty_after == 6
assert result.dest_qty_after == 5
Current v0.1.0 surface
UseCaseexecution shell with validation, state loading, policy checks, transitions, transactions, audit, events, jobs, and idempotency.Resultwrapper for use cases that want to return metadata for default audit/event/job dispatch.- In-memory audit, event, job, and idempotency implementations for examples and tests.
- Adapter protocols for policy engines, state machines, workflow engines, event buses, and job queues.
- Canonical
MoveInventoryexample with validation, repository state loading, policy checks, invariant checks, audit, events, jobs, and idempotency replay.
Where it fits
FastAPI
-> Command model
-> UseCaseCore
-> Repositories / Session
-> SQLModel / SQLAlchemy
-> Postgres
Alembic evolves schema.
What it is not
- not an API framework
- not an ORM
- not a database
- not a migration tool
- not a BPM suite
- not a no-code workflow builder
- not a universal rules engine
Repository layout
src/usecasecore/ core package
src/usecasecore/adapters/ adapter protocols
examples/move_inventory/ canonical example
examples/fastapi_sqlalchemy_inventory/
FastAPI + SQLAlchemy example
docs/ documentation stubs
tests/ lifecycle and example tests
index.html homepage
Docs and Examples
- Quickstart
- Concepts
- Architecture
- Adapters
- MoveInventory example
- Example source
- FastAPI + SQLAlchemy inventory example
Release Process
Publishing is manual for now. Do not upload to PyPI until CI is green.
Build and check the package:
rm -rf build dist src/usecasecore.egg-info
python3 -m build
python3 -m twine check dist/*
For TestPyPI, upload with a TestPyPI token:
python3 -m twine upload --repository testpypi dist/*
Then verify install in a clean environment:
python3 -m venv /tmp/ucc-test
source /tmp/ucc-test/bin/activate
python3 -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple \
usecasecore==0.1.0a3
python3 -c "from usecasecore import Command, Result, ExecutionContext, UseCase; print('OK')"
deactivate
Only publish to PyPI after the TestPyPI install works.
Status
Early alpha. The current package release is 0.1.0a3; the core abstractions
are intentionally small while the execution model settles.
Metadata
Release files for usecasecore 0.1.0a3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| usecasecore-0.1.0a3.tar.gz | 14.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| usecasecore-0.1.0a3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.8 kB
Release files / usecasecore-0.1.0a3.tar.gz
| Download URL | usecasecore-0.1.0a3.tar.gz |
|---|---|
| Size | 14.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd2a679fb3ee88b75b493f4c1c3fee411cb72469e2a6091af3c78bfe71d5a225
|
|
BLAKE2b-256 checksum How to use checksums |
0676433bc12a170930ae408b17d9605d109f5e7c7386b437a279a4f9944f2128
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / usecasecore-0.1.0a3-py3-none-any.whl
| Download URL | usecasecore-0.1.0a3-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3b62ee59be39b926302f563db03e5483259f21d7beb2c264fb8722794976b0cb
|
|
BLAKE2b-256 checksum How to use checksums |
fb073b530ca71a85120b88664f863a4bf355503795eea881d8d5ba569dd4327a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|