Skip to main content

The standard runtime for application use cases.

Project description

UseCaseCore

The standard runtime for application use cases.

CI PyPI Python License

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

  • UseCase execution shell with validation, state loading, policy checks, transitions, transactions, audit, events, jobs, and idempotency.
  • Result wrapper 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 MoveInventory example 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

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

usecasecore-0.1.0a3.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

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

usecasecore-0.1.0a3-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

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

Hashes for usecasecore-0.1.0a3.tar.gz
Algorithm Hash digest
SHA256 dd2a679fb3ee88b75b493f4c1c3fee411cb72469e2a6091af3c78bfe71d5a225
MD5 5dc4325cb3b20957397e6107e20cd268
BLAKE2b-256 0676433bc12a170930ae408b17d9605d109f5e7c7386b437a279a4f9944f2128

See more details on using hashes here.

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

Hashes for usecasecore-0.1.0a3-py3-none-any.whl
Algorithm Hash digest
SHA256 3b62ee59be39b926302f563db03e5483259f21d7beb2c264fb8722794976b0cb
MD5 d230a91a87c156d92e9d6d22b331a590
BLAKE2b-256 fb073b530ca71a85120b88664f863a4bf355503795eea881d8d5ba569dd4327a

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