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.2.0.tar.gz (31.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_crud_tx-2.2.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: sqlalchemy_crud_tx-2.2.0.tar.gz
  • Upload date:
  • Size: 31.2 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.0.tar.gz
Algorithm Hash digest
SHA256 9bb2bac83274bfdb631f89df165bd1b582546d571d1419b621e2a70d0a54cae0
MD5 f6e96e3fef7ce5b9c79a080e14660b52
BLAKE2b-256 34e4be651ab79fa5769df5ff2c95adeaf7834a444c4b3aa5370e17d5daebc363

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for sqlalchemy_crud_tx-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a06df0658ca132298577c5cc136a18452b7ff06a70afec06c2a3f23d63a82891
MD5 de27f3eb9333a52ab265bc8594c3d68d
BLAKE2b-256 64f186504f0ef9d4754c14ee226d6529954918fe7e512bec4eef7f78ac7e57a1

See more details on using hashes here.

Provenance

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