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.2.tar.gz (33.5 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.2-py3-none-any.whl (37.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: sqlalchemy_crud_tx-2.2.2.tar.gz
  • Upload date:
  • Size: 33.5 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.2.tar.gz
Algorithm Hash digest
SHA256 2972f6e8fada169858b1f44f3f320725971442ee33e59cfccdc3268dd0e09354
MD5 a246a195828966cbb7dbf2628bc1af2a
BLAKE2b-256 597df2889b615678c3432a036c99e7a709e22f14ed56af98748939c837dca138

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for sqlalchemy_crud_tx-2.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2d53d219bf0e1d898bceeaa33ac490f69069d3dbabe03c814b2bba4b5ba0688c
MD5 b77c58cee3119785ac86ee39dbd40457
BLAKE2b-256 bbd347ae7396732a47bfba575e2bd16ae9520e006fd257ca09044620ce4ef0d4

See more details on using hashes here.

Provenance

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