Skip to main content

Spring-style transactional boundaries for SQLAlchemy async sessions.

Project description

sqlalchemy-transactional

Spring-style transactional boundaries for SQLAlchemy async sessions. Inspired by Spring Framework's @Transactional model.

Quick Start (FastAPI)

Assume you already run FastAPI with async_sessionmaker, and want Spring-like declarative transaction boundaries in service methods.

from fastapi import FastAPI, Request
from pydantic import BaseModel
from sqlalchemy import text
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

from sqlalchemy_transactional.asyncio import (
    current_session,
    sessionmaker_context,
    transactional,
)
from sqlalchemy_transactional.common import Propagation

app = FastAPI()
engine = create_async_engine("sqlite+aiosqlite:///app.db")
sessionmaker = async_sessionmaker(engine, expire_on_commit=False)


# 1) Bind async_sessionmaker once per request boundary
@app.middleware("http")
async def transactional_context_middleware(request: Request, call_next):
    async with sessionmaker_context(sessionmaker):
        return await call_next(request)


class CreateUserRequest(BaseModel):
    name: str


# 2) Put transaction boundaries on service methods
class UserService:
    @transactional  # default propagation = REQUIRED
    async def create_user(self, name: str) -> None:
        await self._validate_name(name)
        await self._insert_user(name)

    @transactional(Propagation.MANDATORY)
    async def _validate_name(self, name: str) -> None:
        result = await current_session().execute(
            text("SELECT 1 FROM users WHERE name = :name"),
            {"name": name},
        )
        if result.first() is not None:
            raise ValueError("name already exists")

    @transactional(Propagation.MANDATORY)
    async def _insert_user(self, name: str) -> None:
        await current_session().execute(
            text("INSERT INTO users (name) VALUES (:name)"),
            {"name": name},
        )


user_service = UserService()


# 3) Route/controller stays thin
@app.post("/users")
async def create_user(payload: CreateUserRequest) -> dict[str, str]:
    await user_service.create_user(payload.name)
    return {"status": "ok"}

Spring-to-FastAPI Mapping

Spring concept FastAPI + sqlalchemy-transactional
@Transactional on service methods @transactional on async service methods
@Transactional(propagation = REQUIRED) @transactional (default = Propagation.REQUIRED)
@Transactional(propagation = MANDATORY) @transactional(Propagation.MANDATORY)
Request filter/interceptor binds tx resources @app.middleware("http") + sessionmaker_context(sessionmaker)
Current tx-bound resource lookup current_session()

Why Use This

  • Keep transaction plumbing out of service code.
  • Declare transaction scope at the function boundary.
  • Use familiar propagation semantics from Spring (REQUIRED, MANDATORY, etc.).
  • Apply isolation level declaratively when needed.
  • Make service methods easier to read, test, and review.

Propagation Modes

  • REQUIRED (default): Join an active transaction, or create one if none exists.
  • MANDATORY: Require an active transaction; raise an error if missing.
  • REQUIRES_NEW: Always execute in a new transaction.
  • NESTED: Use savepoint semantics inside an active transaction; otherwise behave like REQUIRED.

Isolation Level

Set isolation level at the decorator boundary:

@transactional(isolation_level="SERIALIZABLE")
async def run_settlement() -> None:
    await current_session().execute(...)

If isolation_level is omitted (None, the default), SQLAlchemy uses the engine/dialect default isolation behavior from your database driver.

Combine with propagation when needed:

@transactional(Propagation.REQUIRES_NEW, isolation_level="SERIALIZABLE")
async def write_critical_audit_log() -> None:
    await current_session().execute(...)

When isolation_level is applied:

  • The decorator creates a new transaction (REQUIRES_NEW, or REQUIRED / NESTED when no active transaction exists).

When isolation_level is not applied by this decorator:

  • The function joins an already active transaction (REQUIRED with an active transaction, MANDATORY, or NESTED with an active transaction).

Contributing

Development workflow, checks, and test policies are defined in AGENTS.md.

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

sqlalchemy_transactional-1.0.0.tar.gz (5.2 kB view details)

Uploaded Source

Built Distribution

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

sqlalchemy_transactional-1.0.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file sqlalchemy_transactional-1.0.0.tar.gz.

File metadata

File hashes

Hashes for sqlalchemy_transactional-1.0.0.tar.gz
Algorithm Hash digest
SHA256 d9ff74a737d1d38aa5afd01f05f8891d0273f9781ec0a03830a8c26c2d8794ae
MD5 40669be9c4b3d07436b13cad35870d2e
BLAKE2b-256 7e0ab902b3cac23cc56deef37d1abe098b2c9e3a7fe337e36b217f20cd06e50a

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_transactional-1.0.0.tar.gz:

Publisher: pypi-publish.yml on harryplusplus/sqlalchemy-transactional

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sqlalchemy_transactional-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sqlalchemy_transactional-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a4b9b6871585ba0a387968e81510a95361b2b844f706ef9c03527477c00690f2
MD5 69fd7849c16ba0155869f46d27668ba3
BLAKE2b-256 ab9facab3bd8edc62791d99d1d5eb841d94dc2cb76a4d7ab4f589bd01e5539d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_transactional-1.0.0-py3-none-any.whl:

Publisher: pypi-publish.yml on harryplusplus/sqlalchemy-transactional

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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