SQLAlchemy db context
Project description
sqladbx
sqladbx — async-first SQLAlchemy middleware and database proxy for FastAPI, Temporal, and other async Python frameworks.
Provides a global async session context (db.session) with seamless integration into existing codebases. Works with SQLAlchemy 2.0+.
Features
✨ Core Features:
- 🔄 Async SQLAlchemy 2.0+ integration with
AsyncSession - 🎯 Global session context with
db.sessionpattern - 📦 Database proxy with flexible initialization
- 🔌 Starlette/FastAPI middleware support
- 🔀 Multi-database support (master/replica pattern)
- ⚡ Multi-session mode for concurrent async tasks
- 🎪 Framework-agnostic (works with FastAPI, Litestar, Temporal, etc.)
- 💾 No middleware required (works in workers, CLI, scripts)
- ✅ 97% test coverage with 47+ tests across multiple frameworks
- 🔄 Seamless backward compatibility with existing codebases
Installation
pip install sqladbx
Requirements
- Python 3.13+
- SQLAlchemy 2.0+
- asyncpg or other async database driver
Quick Start
Basic Setup
from fastapi import FastAPI
from sqladbx import SQLAlchemyMiddleware, db
app = FastAPI()
# Add middleware to initialize the database
app.add_middleware(
SQLAlchemyMiddleware,
db_url="postgresql+asyncpg://user:password@localhost/dbname",
engine_args={
"echo": True,
"pool_size": 10,
"max_overflow": 20,
}
)
Using Sessions
from fastapi import APIRouter
from sqlalchemy import select
from sqlmodel import SQLModel, Field
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
email: str
router = APIRouter()
@router.get("/users")
async def get_users():
"""Read users from database."""
result = await db.session.execute(select(User))
return result.scalars().all()
@router.post("/users")
async def create_user(name: str, email: str):
"""Create a new user."""
user = User(name=name, email=email)
db.session.add(user)
await db.session.flush()
return user
Advanced Usage
Multiple Databases (Master/Replica Pattern)
from sqladbx import create_db_middleware, db
from sqladbx.proxy import DBProxy
# Create independent database proxies
master_db = db # Use default proxy
replica_db = DBProxy() # Create new proxy for replica
# Initialize master database
app.add_middleware(
SQLAlchemyMiddleware,
db_url="postgresql+asyncpg://user:password@localhost/master_db",
db_proxy=master_db,
engine_args={"pool_size": 10}
)
# Initialize replica database using factory pattern
ReplicaMiddleware = create_db_middleware()
app.add_middleware(
ReplicaMiddleware,
db_url="postgresql+asyncpg://user:password@localhost/replica_db",
db_proxy=replica_db,
engine_args={"pool_size": 10}
)
# Route handlers manage their own context
@router.get("/users")
async def get_users():
result = await master_db.session.execute(select(User))
return result.scalars().all()
@router.get("/users/cached")
async def get_users_cached():
result = await replica_db.session.execute(select(User))
return result.scalars().all()
Without Middleware (Workers, CLI, Scripts)
from sqladbx import db
# Initialize anywhere - no middleware required
async def main():
db.initialize(
create_engine(
"postgresql+asyncpg://user:password@localhost/dbname",
echo=True
),
session_args={}
)
async with db():
result = await db.session.execute(select(User))
users = result.scalars().all()
print(users)
Multi-Session Mode (Concurrent Tasks)
For parallel async operations within a single request:
@router.post("/bulk-operations")
async def bulk_operations():
"""Execute multiple database operations concurrently."""
async with db(multi_sessions=True):
# Each db.session call creates a new session
tasks = [
process_task(1),
process_task(2),
process_task(3),
]
results = await asyncio.gather(*tasks)
return results
async def process_task(task_id: int):
# Each task gets its own session
result = await db.session.execute(select(User).where(User.id == task_id))
return result.scalar_one()
Commit Control
# Automatic commit on context exit
async with db(commit_on_exit=True):
user = User(name="John", email="john@example.com")
db.session.add(user)
# Auto-commits on __aexit__
# Manual commit control (default)
async with db():
user = User(name="Jane", email="jane@example.com")
db.session.add(user)
await db.session.flush() # Explicit flush
# Auto-rollback on context exit
Framework Integration
FastAPI
See Quick Start section above.
Litestar
from litestar import Litestar
from sqladbx import SQLAlchemyMiddleware
app = Litestar(
route_handlers=[...],
middleware=[
MiddlewareProtocolConfig(
middleware=SQLAlchemyMiddleware,
kwargs={
"db_url": "postgresql+asyncpg://...",
}
)
]
)
Temporal
from sqladbx import db
from temporalio import workflow
@workflow.defn
class MyWorkflow:
@workflow.run
async def run(self, name: str) -> str:
# Initialize db without middleware
db.initialize(engine, session_args={})
async with db():
# Use session in activities
result = await db.session.execute(select(User))
users = result.scalars().all()
return f"Processed {len(users)} users"
Background Tasks (TaskIQ)
from sqladbx import db
from taskiq import InMemoryBroker
broker = InMemoryBroker()
@broker.task
async def process_user(user_id: int):
db.initialize(engine, session_args={})
async with db():
result = await db.session.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one()
# Process user...
API Reference
SQLAlchemyMiddleware
Main middleware for FastAPI/Starlette applications.
SQLAlchemyMiddleware(
app: ASGIApp,
*,
db_url: str | URL | None = None,
custom_engine: AsyncEngine | None = None,
engine_args: dict[str, object] | None = None,
session_args: dict[str, object] | None = None,
commit_on_exit: bool = False,
db_proxy: DBProxy | None = None,
)
Parameters:
db_url: Database connection stringcustom_engine: Pre-configuredAsyncEngine(overridesdb_url)engine_args: SQLAlchemy engine creation argumentssession_args: Session factory argumentscommit_on_exit: Auto-commit on context exitdb_proxy: CustomDBProxyinstance (defaults to globaldb)
DBProxy
Database proxy for managing async sessions.
from sqlax import db
from sqlax.proxy import DBProxy
# Global proxy (pre-created)
db: DBProxy
# Create custom proxy
replica_db = DBProxy()
Methods:
initialize(engine, session_args): Initialize with AsyncEnginesession: Get current session (within context)__call__(commit_on_exit=False, multi_sessions=False): Async context manager
create_db_middleware()
Factory function for creating independent middleware classes:
from sqladbx import create_db_middleware
ReplicaMiddleware = create_db_middleware()
app.add_middleware(
ReplicaMiddleware,
db_url="...",
db_proxy=replica_db,
)
Testing
The library includes comprehensive test coverage:
# Run all tests
pytest
# Run with coverage
pytest --cov=sqlax --cov-report=html
# Run specific test suite
pytest tests/integration/test_fastapi_integration.py
Test Coverage: 97% (47+ tests)
- Unit tests for proxy, sessions, exceptions
- FastAPI integration tests
- Temporal workflow tests
- Litestar integration tests
- TaskIQ background task tests
Error Handling
Common Exceptions
from sqladbx.exceptions import (
SessionNotInitialisedError, # DB not initialized
MissingSessionError, # No session in context
)
try:
async with db():
result = await db.session.execute(select(User))
except MissingSessionError:
print("Session context required")
except SessionNotInitialisedError:
print("Database not initialized")
Design Principles
- Simplicity: Zero-boilerplate session management
- Flexibility: Works with or without middleware
- Performance: Efficient async/await patterns, connection pooling
- Type Safety: Full type hints for IDE autocomplete
- Testing: Comprehensive test suite across frameworks
- Production Ready: Used in high-throughput systems
Migration Guide
From fastapi-async-sqlalchemy
If migrating from fastapi_async_sqlalchemy:
# Old code
from fastapi_async_sqlalchemy import get_db
@router.get("/users")
async def get_users(db = Depends(get_db)):
result = await db.execute(select(User))
return result.scalars().all()
# New code - simpler!
from sqladbx import db
@router.get("/users")
async def get_users():
async with db():
result = await db.session.execute(select(User))
return result.scalars().all()
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure 97%+ coverage
- Submit a pull request
License
MIT License - see LICENSE file for details
Support
Made with ❤️ for the Python async community
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 sqladbx-0.0.2.tar.gz.
File metadata
- Download URL: sqladbx-0.0.2.tar.gz
- Upload date:
- Size: 17.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f94104d1c3319e864127588d82532732542be01399ef82444d9eefa6e8065bb4
|
|
| MD5 |
877f91a5ae6c2487275e3da2cfea4195
|
|
| BLAKE2b-256 |
04190c4756958113ccd4c7dfefb570615c0addb00b2bf2e60ec1a0185e085a31
|
File details
Details for the file sqladbx-0.0.2-py3-none-any.whl.
File metadata
- Download URL: sqladbx-0.0.2-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf99ac0bd54aef8704947d9aa331d369f0e8a43eea141944c6258f11c616411f
|
|
| MD5 |
2bed46af274510846e2debb0c73df11e
|
|
| BLAKE2b-256 |
cfc02868acbdfb472aafadaf660df16b87d4f2224c7c2b61259cf07cbee6c84c
|