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()removedCRUDQueryremovedconfigure(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.emailmay 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; checkcrud.status/crud.errorinstead.
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
- Provide a DB URI via env or
.env:TEST_DB=sqlite:///./test.db(or another driver). - 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), setexisting_txn_policyinCRUD.configure(...)to control behaviour (error,join,savepoint,adopt_autobegin,reset).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc42fc6243ff233b44e2eadd6bac6e12172348c7aaca565e82e91e858d139848
|
|
| MD5 |
161b5d829e5b96df2fe1c86d3e18438d
|
|
| BLAKE2b-256 |
134916faaee77b226fec8b2789d642248e0cef2958aeb3dd1fc30b11d02aa9f0
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlalchemy_crud_tx-2.1.0.tar.gz -
Subject digest:
bc42fc6243ff233b44e2eadd6bac6e12172348c7aaca565e82e91e858d139848 - Sigstore transparency entry: 1048336459
- Sigstore integration time:
-
Permalink:
ZM-Kimu/sqlalchemy-crud-tx@230d54aecfc38e026cbf01d3ef5507b6d7f07e90 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/ZM-Kimu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@230d54aecfc38e026cbf01d3ef5507b6d7f07e90 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sqlalchemy_crud_tx-2.1.0-py3-none-any.whl.
File metadata
- Download URL: sqlalchemy_crud_tx-2.1.0-py3-none-any.whl
- Upload date:
- Size: 31.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03704edf51a452280c24f9436db8873382d305d4d82b77afb4745d872b11dcc0
|
|
| MD5 |
6a0a9c38442f09b61b02a5bdb6e3bdec
|
|
| BLAKE2b-256 |
5c8082479a9f3cdea08f7a4c1934c931af2e2a5f3cccb5885c7176c7defd6c8f
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlalchemy_crud_tx-2.1.0-py3-none-any.whl -
Subject digest:
03704edf51a452280c24f9436db8873382d305d4d82b77afb4745d872b11dcc0 - Sigstore transparency entry: 1048336519
- Sigstore integration time:
-
Permalink:
ZM-Kimu/sqlalchemy-crud-tx@230d54aecfc38e026cbf01d3ef5507b6d7f07e90 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/ZM-Kimu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@230d54aecfc38e026cbf01d3ef5507b6d7f07e90 -
Trigger Event:
push
-
Statement type: