The standard runtime for application use cases.
Project description
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.
Project details
Release history Release notifications | RSS feed
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 usecasecore-0.1.0a3.tar.gz.
File metadata
- Download URL: usecasecore-0.1.0a3.tar.gz
- Upload date:
- Size: 14.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd2a679fb3ee88b75b493f4c1c3fee411cb72469e2a6091af3c78bfe71d5a225
|
|
| MD5 |
5dc4325cb3b20957397e6107e20cd268
|
|
| BLAKE2b-256 |
0676433bc12a170930ae408b17d9605d109f5e7c7386b437a279a4f9944f2128
|
File details
Details for the file usecasecore-0.1.0a3-py3-none-any.whl.
File metadata
- Download URL: usecasecore-0.1.0a3-py3-none-any.whl
- Upload date:
- Size: 14.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b62ee59be39b926302f563db03e5483259f21d7beb2c264fb8722794976b0cb
|
|
| MD5 |
d230a91a87c156d92e9d6d22b331a590
|
|
| BLAKE2b-256 |
fb073b530ca71a85120b88664f863a4bf355503795eea881d8d5ba569dd4327a
|