Skip to main content

Use SQLAlchemy in a Pythonic way.

Project description

sqlalchemy-crud-tx

English | 中文

A lightweight CRUD + transaction helper for SQLAlchemy:

  • Context-managed CRUD with nested savepoints: with CRUD(Model) as crud:
  • Function-level transactions via @CRUD.transaction() with join semantics
  • Configurable error policy (error_policy="raise"|"status_only") and pluggable logger
  • SQLAlchemy 2.x typed query helpers (select/execute/scalars/scalar)

Install

pip install sqlalchemy-crud-tx
# or local editable install
pip install -e .

# async extras (driver + async test tooling)
pip install "sqlalchemy-crud-tx[asyncio]"

Requires Python 3.11+ with sqlalchemy>=2.0.

Async Namespace (sqlalchemy_crud_tx.asyncio)

The top-level import synchronous: from sqlalchemy_crud_tx import CRUD

For async usage, import CRUD from the asyncio namespace: from sqlalchemy_crud_tx.asyncio import CRUD

import asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy_crud_tx.asyncio import CRUD

engine = create_async_engine("sqlite+aiosqlite:///./async_demo.db")
SessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
# assume User is a normal SQLAlchemy declarative model

async def run():
    CRUD.configure(session_provider=SessionLocal, error_policy="raise")
    async with CRUD(User) as crud:
        await crud.add(email="async@example.com")

    @CRUD.transaction()
    async def write_two() -> None:
        async with CRUD(User) as c1:
            await c1.add(email="a@example.com")
        async with CRUD(User) as c2:
            await c2.add(email="b@example.com")

    await write_two()

asyncio.run(run())

Quick Start (pure SQLAlchemy)

from sqlalchemy import String, Integer, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
from sqlalchemy_crud_tx import CRUD

engine = create_engine("sqlite:///./crud_example.db", echo=False)
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "example_user"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)


Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)

CRUD.configure(session_provider=SessionLocal, error_policy="raise")

with CRUD(User) as crud:
    user = crud.add(email="demo@example.com")
    print("created", user)

with CRUD(User, email="demo@example.com") as crud:
    row = crud.first()
    print("fetched", row)

with CRUD(User) as crud:
    updated = crud.update(row, email="updated@example.com")
    print("updated", updated)

with CRUD(User, email="updated@example.com") as crud:
    crud.delete()

SQLAlchemy 2.x Typed Queries

from sqlalchemy import select, func

with CRUD(User, email="demo@example.com") as crud:
    # Build Select from CRUD defaults
    stmt = crud.select().order_by(User.id)
    users = crud.scalars(stmt).all()  # list[User]

    # Project columns -> row objects (not ORM model instances)
    rows = crud.execute(crud.select(User.id, User.email)).all()
    first_email = rows[0].email

    # Scalar helper
    total = crud.scalar(select(func.count(User.id)))

2.0 Breaking Changes

Legacy Query-based APIs were removed in 2.0.0:

  • CRUD.query() removed
  • CRUDQuery removed
  • configure(query_builder=...) removed
  • Built-in paginate(...) removed

Migration quick map:

Before After
crud.query().all() crud.all() or crud.scalars(crud.select()).all()
crud.query().filter(...).first() crud.first(crud.select().where(...))
crud.query().with_entities(User.id, User.email).all() crud.execute(crud.select(User.id, User.email)).all()
crud.query().order_by(...).paginate(...) explicit count + limit + offset in user code

Typing notes:

  • Runtime row attribute access like row.email may work.
  • Static hard guarantee is based on tuple-position types (row[0], row[1]).

Function-Level Transactions

from sqlalchemy_crud_tx import CRUD

CRUD.configure(session_provider=SessionLocal, error_policy="raise")

@CRUD.transaction(error_policy="raise")
def create_two_users():
    with CRUD(User) as crud1:
        crud1.add(email="a@example.com")
    with CRUD(User) as crud2:
        crud2.add(email="b@example.com")

create_two_users()
  • The outermost call commits or rolls back; inner CRUD contexts only mark status when exceptions occur.
  • With error_policy="status_only", SQLAlchemyError is rolled back and caught; check crud.status / crud.error instead.

Docs & Examples

  • Full example: src/sqlalchemy_crud_tx/examples/basic_crud.py
  • Async example: src/sqlalchemy_crud_tx/examples/async_basic_crud.py
  • Mixed API example: src/sqlalchemy_crud_tx/examples/mixed_full_api.py
  • Transaction patterns: src/sqlalchemy_crud_tx/examples/transaction_patterns.py
  • Archived transaction refactor notes: docs/archive/crud_refactor_todo.md
  • Archived typing directions: docs/archive/todo.md

Testing

  1. Provide a DB URI via env or .env: TEST_DB=sqlite:///./test.db (or another driver).
  2. Install test deps, then:
    pytest -q
    

Notes

  • Always call CRUD.configure(session_provider=...) before using CRUD instances.
  • Type checking baseline is defined by Pylance/Pyright strict (pyrightconfig.json).
  • If a Session may already be in a transaction (for example, AUTOBEGIN after expire_on_commit), set existing_txn_policy in CRUD.configure(...) to control behaviour (error, join, savepoint, adopt_autobegin, reset).

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_crud_tx-2.2.1.tar.gz (32.6 kB view details)

Uploaded Source

Built Distribution

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

sqlalchemy_crud_tx-2.2.1-py3-none-any.whl (36.6 kB view details)

Uploaded Python 3

File details

Details for the file sqlalchemy_crud_tx-2.2.1.tar.gz.

File metadata

  • Download URL: sqlalchemy_crud_tx-2.2.1.tar.gz
  • Upload date:
  • Size: 32.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sqlalchemy_crud_tx-2.2.1.tar.gz
Algorithm Hash digest
SHA256 26f4d7d11dadcfaf5d6449e04d45fb6de1e6a7f80dd04271f77cadf23050032f
MD5 fa48549c59684c2061d8b60b0045daf3
BLAKE2b-256 73a3236b86e771cf0804e93867fec292e464c49aca22d64531aab3c0cc68822f

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_crud_tx-2.2.1.tar.gz:

Publisher: publish-pypi.yml on ZM-Kimu/sqlalchemy-crud-tx

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_crud_tx-2.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for sqlalchemy_crud_tx-2.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2a29cec30b9f31f854421a1f5e572b26290f543d3c2f8f0263b07cb299109981
MD5 f43d46a5b0bdf8bbadff367acb074eae
BLAKE2b-256 8d8d3b47a207de82aec1a56a597b44cce409f51b3288be33a25bfb5013689145

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_crud_tx-2.2.1-py3-none-any.whl:

Publisher: publish-pypi.yml on ZM-Kimu/sqlalchemy-crud-tx

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