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: docs/examples/basic_crud.py
  • Async example: docs/examples/async_basic_crud.py
  • Transaction refactor notes/TODO: docs/crud_refactor_todo.md
  • Typing directions: docs/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.1.0.tar.gz (30.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.1.0-py3-none-any.whl (31.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: sqlalchemy_crud_tx-2.1.0.tar.gz
  • Upload date:
  • Size: 30.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.1.0.tar.gz
Algorithm Hash digest
SHA256 bc42fc6243ff233b44e2eadd6bac6e12172348c7aaca565e82e91e858d139848
MD5 161b5d829e5b96df2fe1c86d3e18438d
BLAKE2b-256 134916faaee77b226fec8b2789d642248e0cef2958aeb3dd1fc30b11d02aa9f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_crud_tx-2.1.0.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.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sqlalchemy_crud_tx-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 03704edf51a452280c24f9436db8873382d305d4d82b77afb4745d872b11dcc0
MD5 6a0a9c38442f09b61b02a5bdb6e3bdec
BLAKE2b-256 5c8082479a9f3cdea08f7a4c1934c931af2e2a5f3cccb5885c7176c7defd6c8f

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlalchemy_crud_tx-2.1.0-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