Metadata-Version: 2.4
Name: af-fastapi-db-txs
Version: 0.0.2
Summary: FastAPI middleware and dependencies for request-scoped DB transaction management
License: MIT
Author: Allfly
Author-email: engineering@allfly.io
Requires-Python: >=3.13,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: af-db-session (>=0.0.3)
Requires-Dist: fastapi (>=0.100.0)
Requires-Dist: starlette (>=0.27.0)
Description-Content-Type: text/markdown

# af-fastapi-db-txs

FastAPI integration for `allfly.db.session.TransactionContext` (from
[af-db-session](https://pypi.org/project/af-db-session/)): request-scoped read-write and
read-only database sessions with automatic commit/rollback, so route handlers never manage
session lifecycle themselves.

**No hidden global state, no DI framework coupling.** Every component here takes its
`TransactionContext` and session factories as explicit arguments — your app owns
instantiation and lifecycle, and wires the same `TransactionContext` instance into the
middleware and any read-only dependencies it builds. `auto_configure_database` (from
af-db-session) builds all of them for you in one call, if you don't need custom factories.

## Installation

```bash
pip install af-fastapi-db-txs
# or with Poetry:
poetry add af-fastapi-db-txs
```

## Wiring it into your app

Your app should hold exactly one `TransactionContext` instance for the life of the process.
The easiest way to get one — along with its matching `DatabaseSessionFactory`/
`RODatabaseSessionFactory` — is `auto_configure_database`, from af-db-session:

```python
from allfly.db.session import auto_configure_database, DatabaseAutoConfigurationProperties
from allfly.fastapi.db_txs import TransactionMiddleware, make_read_only_transaction

db = auto_configure_database(db_settings, DatabaseAutoConfigurationProperties(build_read_replica=True))

app.add_middleware(
    TransactionMiddleware,
    transaction_context=db.transaction_context,
    session_factory=db.session_factory,
)

ReadOnlyTransaction = make_read_only_transaction(db.ro_session_factory, db.transaction_context)
```

If you'd rather assemble the pieces yourself (e.g. a custom `DatabaseSessionFactory`
subclass), build each one directly instead — `TransactionMiddleware` and
`make_read_only_transaction` only need a `TransactionContext` and the relevant factory,
however you got them:

```python
from allfly.db.session import TransactionContext, build_session_factory, build_ro_session_factory

transaction_context = TransactionContext()
session_factory = build_session_factory(db_settings)
ro_session_factory = build_ro_session_factory(db_settings)
```

## Using it in routes

Write routes (RW by default — the middleware opens a session for every request):

```python
@router.post("/users")
def create_user(user_repo: UserRepositoryDI):
    return user_repo.save(user)  # commits automatically on a 2xx/3xx response
```

Read-only routes (route or router-level, targets your read replica instead):

```python
@router.get("/users", dependencies=[ReadOnlyTransaction])
def search_users(user_repo: UserRepositoryDI) -> list[UserResponse]:
    ...
```

Wherever your app resolves a `Session` for its repositories, call
`transaction_context.get_session()` — it returns the RO session if one is active for the
current request, otherwise the RW session opened by the middleware, otherwise `None`.

## Scripts and background tasks

For code outside a FastAPI request (management scripts, workers), use the context managers
directly instead of the middleware/dependency — either via the `db` object from
`auto_configure_database`:

```python
with db.transaction():
    user_repo = UserRepository(db.transaction_context.get_session())
    user_repo.save(user)
    # commits on success, rolls back on exception

with db.read_transaction():
    result = repo.find(...)
```

or, if you built the pieces yourself, the same methods on `TransactionContext` directly:

```python
with transaction_context.transaction(session_factory):
    user_repo = UserRepository(transaction_context.get_session())
    user_repo.save(user)

with transaction_context.read_transaction(ro_session_factory):
    result = repo.find(...)
```

If a single `DatabaseAutoConfiguration` is truly the only one in the process (no multiple
databases, no multiple apps sharing the interpreter — e.g. in a pytest session), af-db-session
also has an opt-in `allfly.db.session.default` module for a bare `with transaction():` — see
af-db-session's docs for when that tradeoff is worth it.

## Why explicit injection?

This library has no knowledge of any dependency-injection framework. `TransactionMiddleware`
and `make_read_only_transaction` both take a `TransactionContext` and session factory
instances directly — if your app uses a DI container, wire these components into it yourself;
if it doesn't, pass the instances around as plain module-level values. Either way, the one
invariant that matters is sharing the *same* `TransactionContext` instance across the
middleware, any read-only dependencies, and your app's own session-resolution code — its
ContextVars are what tie a request's session together.

