
Python Database Patterns
- 1 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
python-database-patterns is a skill that gives SQLAlchemy 2.0 ORM and async database patterns for Python and FastAPI backends.
About
This skill teaches SQLAlchemy 2.0 and database best practices for Python. It covers declarative models, sync and async engines, query patterns, relationships, and FastAPI dependency injection for DB sessions. A developer loads it when writing ORM code, tuning connection pools, or setting up Alembic migrations.
- SQLAlchemy 2.0 ORM and async database patterns
- Query, relationship, and eager-loading recipes
- Alembic migration and connection-pooling references
Python Database Patterns by the numbers
- 1 all-time installs (skills.sh)
- Ranked #765 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
python-database-patterns capabilities & compatibility
- Capabilities
- database · api development
- Works with
- postgres
- Use cases
- database · api development
- Pricing
- Free
What python-database-patterns says it does
SQLAlchemy and database patterns for Python. Triggers on: sqlalchemy, database, orm, migration, alembic, async database, connection pool, repository pattern, unit of work.
SQLAlchemy 2.0 and database best practices.
npx skills add https://github.com/aiskillstore/marketplace --skill python-database-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Write SQLAlchemy 2.0 models, queries, and async sessions in a Python backend or FastAPI app.
Who is it for?
Python developers writing SQLAlchemy 2.0 models, queries, and async database sessions.
Skip if: Non-SQLAlchemy ORMs or non-Python stacks.
When should I use this skill?
Writing SQLAlchemy models, async sessions, Alembic migrations, or FastAPI database dependencies.
What you get
Produces SQLAlchemy 2.0 models, typed queries, async sessions, and migration setup.
- SQLAlchemy 2.0 models
- async session setup
- Alembic migration config
By the numbers
- 7-row SQLAlchemy 2.0 quick-reference table
- 4 reference docs plus 1 alembic.ini template asset
Files
Python Database Patterns
SQLAlchemy 2.0 and database best practices.
SQLAlchemy 2.0 Basics
from sqlalchemy import create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
email: Mapped[str] = mapped_column(String(255), unique=True)
is_active: Mapped[bool] = mapped_column(default=True)
# Create engine and tables
engine = create_engine("postgresql://user:pass@localhost/db")
Base.metadata.create_all(engine)
# Query with 2.0 style
with Session(engine) as session:
stmt = select(User).where(User.is_active == True)
users = session.execute(stmt).scalars().all()Async SQLAlchemy
from sqlalchemy.ext.asyncio import (
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from sqlalchemy import select
# Async engine
engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
echo=False,
pool_size=5,
max_overflow=10,
)
# Session factory
async_session = async_sessionmaker(engine, expire_on_commit=False)
# Usage
async with async_session() as session:
result = await session.execute(select(User).where(User.id == 1))
user = result.scalar_one_or_none()Model Relationships
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
# One-to-many
posts: Mapped[list["Post"]] = relationship(back_populates="author")
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
# Many-to-one
author: Mapped["User"] = relationship(back_populates="posts")Common Query Patterns
from sqlalchemy import select, and_, or_, func
# Basic select
stmt = select(User).where(User.is_active == True)
# Multiple conditions
stmt = select(User).where(
and_(
User.is_active == True,
User.age >= 18
)
)
# OR conditions
stmt = select(User).where(
or_(User.role == "admin", User.role == "moderator")
)
# Ordering and limiting
stmt = select(User).order_by(User.created_at.desc()).limit(10)
# Aggregates
stmt = select(func.count(User.id)).where(User.is_active == True)
# Joins
stmt = select(User, Post).join(Post, User.id == Post.author_id)
# Eager loading
from sqlalchemy.orm import selectinload
stmt = select(User).options(selectinload(User.posts))FastAPI Integration
from fastapi import Depends, FastAPI
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
DB = Annotated[AsyncSession, Depends(get_db)]
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: DB):
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404)
return userQuick Reference
| Operation | SQLAlchemy 2.0 Style |
|---|---|
| Select all | select(User) |
| Filter | .where(User.id == 1) |
| First | .scalar_one_or_none() |
| All | .scalars().all() |
| Count | select(func.count(User.id)) |
| Join | .join(Post) |
| Eager load | .options(selectinload(User.posts)) |
Additional Resources
./references/sqlalchemy-async.md- Async patterns, session management./references/connection-pooling.md- Pool configuration, health checks./references/transactions.md- Transaction patterns, isolation levels./references/migrations.md- Alembic setup, migration strategies
Assets
./assets/alembic.ini.template- Alembic configuration template
---
See Also
Prerequisites:
python-typing-patterns- Mapped types and annotationspython-async-patterns- Async database sessions
Related Skills:
python-fastapi-patterns- Dependency injection for DB sessionspython-pytest-patterns- Database fixtures and testing
# Alembic Configuration Template
# Copy to alembic.ini and customize
[alembic]
# Path to migration scripts
script_location = alembic
# Template for new migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
# Prepend sys.path for model imports
prepend_sys_path = .
# Timezone for file timestamps
# timezone =
# Max length for autogenerate identifiers
# truncate_slug_length = 40
# Post-write hooks
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -q
# Logging
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Connection Pool Configuration
Database connection pool patterns for production.
SQLAlchemy Pool Settings
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import create_async_engine
# Sync engine with pool config
engine = create_engine(
"postgresql://user:pass@localhost/db",
# Pool size
pool_size=5, # Persistent connections (default: 5)
max_overflow=10, # Extra connections when pool exhausted
# Total max connections = pool_size + max_overflow = 15
# Timeouts
pool_timeout=30, # Wait for connection (seconds)
pool_recycle=3600, # Recycle connections after N seconds
pool_pre_ping=True, # Test connections before use
# Connection args
connect_args={
"connect_timeout": 10,
"options": "-c statement_timeout=30000", # 30s query timeout
},
)
# Async engine
async_engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
pool_size=5,
max_overflow=10,
pool_timeout=30,
pool_recycle=3600,
pool_pre_ping=True,
)Pool Sizing Guidelines
"""
Connection Pool Sizing
Rule of thumb:
pool_size = (CPU cores × 2) + disk spindles
For async applications:
pool_size = expected_concurrent_requests / avg_queries_per_request
Examples:
- Web app, 4 cores, SSD: pool_size=10, max_overflow=10
- Worker, 4 cores, HDD: pool_size=12, max_overflow=5
- High-traffic API: pool_size=20, max_overflow=30
"""
import os
def calculate_pool_size() -> tuple[int, int]:
"""Calculate pool size based on environment."""
cpu_count = os.cpu_count() or 4
if os.getenv("ENV") == "production":
pool_size = cpu_count * 2 + 4
max_overflow = pool_size
else:
pool_size = 5
max_overflow = 5
return pool_size, max_overflow
pool_size, max_overflow = calculate_pool_size()Pool Events and Monitoring
from sqlalchemy import event
from sqlalchemy.pool import Pool
import logging
logger = logging.getLogger(__name__)
@event.listens_for(Pool, "connect")
def on_connect(dbapi_conn, connection_record):
"""Called when a new connection is created."""
logger.debug("New database connection created")
@event.listens_for(Pool, "checkout")
def on_checkout(dbapi_conn, connection_record, connection_proxy):
"""Called when a connection is retrieved from pool."""
logger.debug("Connection checked out from pool")
@event.listens_for(Pool, "checkin")
def on_checkin(dbapi_conn, connection_record):
"""Called when a connection is returned to pool."""
logger.debug("Connection returned to pool")
@event.listens_for(Pool, "invalidate")
def on_invalidate(dbapi_conn, connection_record, exception):
"""Called when a connection is invalidated."""
logger.warning(f"Connection invalidated: {exception}")
# Pool statistics
def log_pool_status(engine):
"""Log current pool status."""
pool = engine.pool
logger.info(
f"Pool status: "
f"size={pool.size()}, "
f"checked_out={pool.checkedout()}, "
f"overflow={pool.overflow()}, "
f"checkedin={pool.checkedin()}"
)Health Check Endpoint
from fastapi import FastAPI, HTTPException
from sqlalchemy import text
import asyncio
app = FastAPI()
async def check_database_health(timeout: float = 5.0) -> dict:
"""Check database connectivity and response time."""
try:
start = asyncio.get_event_loop().time()
async with async_session_factory() as session:
await asyncio.wait_for(
session.execute(text("SELECT 1")),
timeout=timeout
)
latency = (asyncio.get_event_loop().time() - start) * 1000
return {
"status": "healthy",
"latency_ms": round(latency, 2),
"pool_size": async_engine.pool.size(),
"pool_checked_out": async_engine.pool.checkedout(),
}
except asyncio.TimeoutError:
return {"status": "unhealthy", "error": "timeout"}
except Exception as e:
return {"status": "unhealthy", "error": str(e)}
@app.get("/health/db")
async def database_health():
health = await check_database_health()
if health["status"] != "healthy":
raise HTTPException(status_code=503, detail=health)
return healthConnection Pool per Service
from dataclasses import dataclass
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
@dataclass
class DatabaseConfig:
url: str
pool_size: int = 5
max_overflow: int = 10
pool_timeout: int = 30
pool_recycle: int = 3600
class DatabasePool:
"""Manage multiple database connections."""
def __init__(self):
self._engines: dict[str, AsyncEngine] = {}
def add_database(self, name: str, config: DatabaseConfig):
"""Add a database connection pool."""
self._engines[name] = create_async_engine(
config.url,
pool_size=config.pool_size,
max_overflow=config.max_overflow,
pool_timeout=config.pool_timeout,
pool_recycle=config.pool_recycle,
pool_pre_ping=True,
)
def get_engine(self, name: str) -> AsyncEngine:
return self._engines[name]
async def close_all(self):
"""Close all connection pools."""
for engine in self._engines.values():
await engine.dispose()
# Usage
db_pool = DatabasePool()
db_pool.add_database("primary", DatabaseConfig(
url="postgresql+asyncpg://user:pass@primary/db",
pool_size=10,
))
db_pool.add_database("replica", DatabaseConfig(
url="postgresql+asyncpg://user:pass@replica/db",
pool_size=20, # More connections for read replica
))Read/Write Splitting
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
# Separate session factories for read/write
write_engine = create_async_engine(
"postgresql+asyncpg://user:pass@primary/db",
pool_size=10,
)
read_engine = create_async_engine(
"postgresql+asyncpg://user:pass@replica/db",
pool_size=20,
)
write_session = async_sessionmaker(write_engine, expire_on_commit=False)
read_session = async_sessionmaker(read_engine, expire_on_commit=False)
# FastAPI dependencies
async def get_write_db():
async with write_session() as session:
yield session
async def get_read_db():
async with read_session() as session:
yield session
WriteDB = Annotated[AsyncSession, Depends(get_write_db)]
ReadDB = Annotated[AsyncSession, Depends(get_read_db)]
@app.get("/users")
async def list_users(db: ReadDB): # Read from replica
result = await db.execute(select(User))
return result.scalars().all()
@app.post("/users")
async def create_user(user: UserCreate, db: WriteDB): # Write to primary
db_user = User(**user.model_dump())
db.add(db_user)
await db.commit()
return db_userGraceful Shutdown
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup - engines already created
yield
# Shutdown - close all pools gracefully
await async_engine.dispose()
logger.info("Database connections closed")
app = FastAPI(lifespan=lifespan)Quick Reference
| Setting | Purpose | Typical Value |
|---|---|---|
pool_size | Persistent connections | 5-20 |
max_overflow | Extra connections | 10-30 |
pool_timeout | Wait for connection | 30s |
pool_recycle | Recycle connection age | 3600s |
pool_pre_ping | Test before use | True |
| Scenario | pool_size | max_overflow |
|---|---|---|
| Development | 5 | 5 |
| Small API | 10 | 10 |
| High-traffic | 20 | 30 |
| Background worker | 5 | 5 |
Database Migrations with Alembic
Schema migration patterns for SQLAlchemy projects.
Setup
# Install
pip install alembic
# Initialize in project root
alembic init alembic
# For async projects
alembic init -t async alembicConfiguration
# alembic/env.py
from logging.config import fileConfig
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
from app.models import Base # Your declarative base
from app.config import settings
config = context.config
# Set database URL from settings
config.set_main_option("sqlalchemy.url", settings.database_url)
target_metadata = Base.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode."""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def do_run_migrations(connection: Connection):
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations():
"""Run migrations in 'online' mode with async engine."""
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
def run_migrations_online():
import asyncio
asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()Common Commands
# Generate migration from model changes
alembic revision --autogenerate -m "add users table"
# Apply all pending migrations
alembic upgrade head
# Rollback one migration
alembic downgrade -1
# Rollback to specific revision
alembic downgrade abc123
# Show current revision
alembic current
# Show migration history
alembic history
# Show pending migrations
alembic history --indicate-currentMigration Script Example
"""Add users table
Revision ID: abc123
Revises:
Create Date: 2024-01-15 10:00:00.000000
"""
from typing import Sequence
from alembic import op
import sqlalchemy as sa
revision: str = 'abc123'
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
'users',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('email', sa.String(255), nullable=False, unique=True),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('is_active', sa.Boolean(), default=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()),
)
op.create_index('ix_users_email', 'users', ['email'])
def downgrade() -> None:
op.drop_index('ix_users_email')
op.drop_table('users')Data Migrations
"""Migrate user names to lowercase
Revision ID: def456
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import table, column
revision = 'def456'
down_revision = 'abc123'
def upgrade() -> None:
# Define table structure for data migration
users = table(
'users',
column('id', sa.Integer),
column('name', sa.String),
)
# Update data
op.execute(
users.update().values(name=sa.func.lower(users.c.name))
)
def downgrade() -> None:
# Data migrations are often one-way
pass
# For complex data migrations
def upgrade() -> None:
connection = op.get_bind()
# Read in batches
results = connection.execute(
sa.text("SELECT id, name FROM users")
)
for batch in results.partitions(1000):
for row in batch:
connection.execute(
sa.text("UPDATE users SET name = :name WHERE id = :id"),
{"id": row.id, "name": row.name.lower()}
)Adding Columns Safely
"""Add nullable column first, then populate
Production-safe column addition for large tables.
"""
def upgrade() -> None:
# Step 1: Add nullable column (fast, no table rewrite)
op.add_column(
'users',
sa.Column('phone', sa.String(20), nullable=True)
)
# Step 2: Populate data (can be done in batches)
# This is often done in a separate migration or script
# Step 3: Add constraint (in a later migration after data is populated)
# op.alter_column('users', 'phone', nullable=False)
def downgrade() -> None:
op.drop_column('users', 'phone')Renaming Columns
"""Rename column with zero downtime
Use a multi-step approach for production.
"""
# Migration 1: Add new column
def upgrade() -> None:
op.add_column('users', sa.Column('full_name', sa.String(200)))
# Copy data
op.execute("UPDATE users SET full_name = name")
def downgrade() -> None:
op.drop_column('users', 'full_name')
# Migration 2: Drop old column (after app updated to use new column)
def upgrade() -> None:
op.drop_column('users', 'name')
def downgrade() -> None:
op.add_column('users', sa.Column('name', sa.String(100)))
op.execute("UPDATE users SET name = full_name")Index Management
"""Add index concurrently (PostgreSQL)
Non-blocking index creation for large tables.
"""
from alembic import op
def upgrade() -> None:
# Create index without locking table (PostgreSQL)
op.execute("""
CREATE INDEX CONCURRENTLY IF NOT EXISTS
ix_users_created_at ON users (created_at)
""")
def downgrade() -> None:
op.execute("DROP INDEX CONCURRENTLY IF EXISTS ix_users_created_at")
# Note: CONCURRENTLY cannot run inside a transaction
# Add to migration script:
# from alembic import context
# context.execute_ddl_statements = TrueMulti-Database Migrations
# alembic.ini
[alembic]
script_location = alembic
[primary]
sqlalchemy.url = postgresql://user:pass@primary/db
[analytics]
sqlalchemy.url = postgresql://user:pass@analytics/db# Run migrations for specific database
alembic -n primary upgrade head
alembic -n analytics upgrade headTesting Migrations
import pytest
from alembic import command
from alembic.config import Config
@pytest.fixture
def alembic_config():
config = Config("alembic.ini")
config.set_main_option("sqlalchemy.url", "sqlite:///:memory:")
return config
def test_migrations_up_down(alembic_config):
"""Test that all migrations apply and rollback cleanly."""
# Apply all migrations
command.upgrade(alembic_config, "head")
# Rollback all migrations
command.downgrade(alembic_config, "base")
# Apply again
command.upgrade(alembic_config, "head")
def test_migration_idempotent(alembic_config):
"""Test migrations can be run multiple times."""
command.upgrade(alembic_config, "head")
command.upgrade(alembic_config, "head") # Should be no-opQuick Reference
| Command | Purpose |
|---|---|
alembic revision --autogenerate -m "msg" | Generate migration |
alembic upgrade head | Apply all migrations |
alembic downgrade -1 | Rollback one |
alembic current | Show current version |
alembic history | List all migrations |
| Operation | Method |
|---|---|
| Create table | op.create_table() |
| Drop table | op.drop_table() |
| Add column | op.add_column() |
| Drop column | op.drop_column() |
| Alter column | op.alter_column() |
| Create index | op.create_index() |
| Execute SQL | op.execute() |
Async SQLAlchemy Patterns
Modern async database patterns with SQLAlchemy 2.0.
Engine and Session Setup
from sqlalchemy.ext.asyncio import (
AsyncSession,
AsyncEngine,
async_sessionmaker,
create_async_engine,
)
# Create async engine
engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
echo=False, # SQL logging
pool_size=5, # Connection pool size
max_overflow=10, # Extra connections allowed
pool_pre_ping=True, # Test connections before use
pool_recycle=3600, # Recycle connections after 1 hour
)
# Session factory (not the session itself)
async_session_factory = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False, # Don't expire objects after commit
)
# Usage with context manager
async def get_users():
async with async_session_factory() as session:
result = await session.execute(select(User))
return result.scalars().all()Session Scopes
# Per-request (FastAPI dependency)
async def get_db():
async with async_session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
# Explicit transaction control
async def transfer_funds(from_id: int, to_id: int, amount: Decimal):
async with async_session_factory() as session:
async with session.begin(): # Auto-commit on success
from_account = await session.get(Account, from_id)
to_account = await session.get(Account, to_id)
from_account.balance -= amount
to_account.balance += amount
# Commits automatically if no exception
# Nested transactions (savepoints)
async def complex_operation():
async with async_session_factory() as session:
async with session.begin():
# Outer transaction
user = User(name="Test")
session.add(user)
try:
async with session.begin_nested(): # Savepoint
# Inner operation that might fail
await risky_operation(session)
except RiskyOperationError:
# Savepoint rolled back, outer continues
pass
await session.commit()Lazy Loading in Async
from sqlalchemy.orm import selectinload, joinedload, subqueryload
# WRONG - lazy loading doesn't work in async
async def bad_example():
async with async_session_factory() as session:
user = await session.get(User, 1)
# This raises an error!
print(user.posts) # MissingGreenlet error
# CORRECT - eager loading
async def good_example():
async with async_session_factory() as session:
# Option 1: selectinload (separate query per relationship)
stmt = select(User).options(selectinload(User.posts))
result = await session.execute(stmt)
user = result.scalar_one()
print(user.posts) # Works!
# Option 2: joinedload (single JOIN query)
stmt = select(User).options(joinedload(User.profile))
result = await session.execute(stmt)
user = result.scalar_one()
# With nested relationships
stmt = select(User).options(
selectinload(User.posts).selectinload(Post.comments)
)Async Session Dependency
from fastapi import Depends
from typing import Annotated, AsyncGenerator
async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""Dependency for FastAPI."""
async with async_session_factory() as session:
yield session
DB = Annotated[AsyncSession, Depends(get_db)]
# With automatic transaction handling
async def get_db_with_transaction() -> AsyncGenerator[AsyncSession, None]:
async with async_session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
finally:
await session.close()Batch Operations
from sqlalchemy import insert, update, delete
# Bulk insert
async def bulk_create_users(users_data: list[dict]):
async with async_session_factory() as session:
stmt = insert(User).values(users_data)
await session.execute(stmt)
await session.commit()
# Bulk update
async def deactivate_users(user_ids: list[int]):
async with async_session_factory() as session:
stmt = (
update(User)
.where(User.id.in_(user_ids))
.values(is_active=False)
)
result = await session.execute(stmt)
await session.commit()
return result.rowcount
# Bulk delete
async def delete_old_posts(before_date: datetime):
async with async_session_factory() as session:
stmt = delete(Post).where(Post.created_at < before_date)
result = await session.execute(stmt)
await session.commit()
return result.rowcount
# Batch processing with chunks
async def process_all_users(batch_size: int = 100):
async with async_session_factory() as session:
offset = 0
while True:
stmt = select(User).offset(offset).limit(batch_size)
result = await session.execute(stmt)
users = result.scalars().all()
if not users:
break
for user in users:
await process_user(user)
await session.commit()
offset += batch_sizeStreaming Results
from sqlalchemy import select
async def stream_large_table():
"""Process large tables without loading all into memory."""
async with async_session_factory() as session:
stmt = select(User).execution_options(yield_per=100)
result = await session.stream(stmt)
async for user in result.scalars():
await process_user(user)
# Partitioned streaming
async def stream_partitioned():
async with async_session_factory() as session:
stmt = select(User).execution_options(yield_per=100)
result = await session.stream(stmt)
async for partition in result.scalars().partitions(100):
# partition is a list of 100 users
await process_batch(partition)Async Raw SQL
from sqlalchemy import text
async def raw_query():
async with async_session_factory() as session:
# Simple query
result = await session.execute(
text("SELECT * FROM users WHERE is_active = :active"),
{"active": True}
)
rows = result.fetchall()
# With column access
for row in rows:
print(row.id, row.name)
async def raw_insert():
async with async_session_factory() as session:
await session.execute(
text("INSERT INTO logs (message) VALUES (:msg)"),
{"msg": "Test log"}
)
await session.commit()Testing with Async
import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
@pytest_asyncio.fixture(scope="session")
async def async_engine():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
await engine.dispose()
@pytest_asyncio.fixture
async def async_session(async_engine):
async with AsyncSession(async_engine) as session:
async with session.begin():
yield session
await session.rollback()
@pytest.mark.asyncio
async def test_create_user(async_session):
user = User(name="Test", email="test@example.com")
async_session.add(user)
await async_session.flush()
assert user.id is not NoneQuick Reference
| Pattern | Async SQLAlchemy |
|---|---|
| Create engine | create_async_engine(url) |
| Session factory | async_sessionmaker(engine) |
| Get session | async with factory() as session: |
| Execute | await session.execute(stmt) |
| Get one | result.scalar_one_or_none() |
| Get all | result.scalars().all() |
| Stream | await session.stream(stmt) |
| Commit | await session.commit() |
| Transaction | async with session.begin(): |
| Eager load | .options(selectinload(rel)) |
Transaction Patterns
Database transaction management for data integrity.
Basic Transaction Patterns
from sqlalchemy.ext.asyncio import AsyncSession
# Pattern 1: Context manager (auto-commit/rollback)
async with async_session_factory() as session:
async with session.begin():
user = User(name="Test")
session.add(user)
# Auto-commits on exit, rollback on exception
# Pattern 2: Explicit control
async with async_session_factory() as session:
try:
user = User(name="Test")
session.add(user)
await session.commit()
except Exception:
await session.rollback()
raise
# Pattern 3: Dependency with auto-commit
async def get_db():
async with async_session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raiseNested Transactions (Savepoints)
async def complex_operation():
async with async_session_factory() as session:
async with session.begin():
# Create user (outer transaction)
user = User(name="Test")
session.add(user)
await session.flush() # Get user.id
try:
# Nested operation (savepoint)
async with session.begin_nested():
profile = Profile(user_id=user.id, bio="Hello")
session.add(profile)
await session.flush()
# This might fail
await validate_profile(profile)
except ValidationError:
# Savepoint rolled back, but user is preserved
logger.warning("Profile creation failed")
# Commit user (profile may or may not exist)
await session.commit()Unit of Work Pattern
from typing import TypeVar, Generic
from sqlalchemy.ext.asyncio import AsyncSession
T = TypeVar("T")
class UnitOfWork:
"""Coordinate multiple repository operations in a transaction."""
def __init__(self, session_factory):
self._session_factory = session_factory
self._session: AsyncSession | None = None
async def __aenter__(self):
self._session = self._session_factory()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
if exc_type:
await self.rollback()
await self._session.close()
async def commit(self):
await self._session.commit()
async def rollback(self):
await self._session.rollback()
@property
def users(self) -> "UserRepository":
return UserRepository(self._session)
@property
def orders(self) -> "OrderRepository":
return OrderRepository(self._session)
# Usage
async def create_order_with_items(user_id: int, items: list):
async with UnitOfWork(async_session_factory) as uow:
user = await uow.users.get(user_id)
if not user:
raise NotFoundError("User not found")
order = Order(user_id=user_id)
order = await uow.orders.add(order)
for item in items:
await uow.orders.add_item(order.id, item)
await uow.commit()
return orderIsolation Levels
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
# Engine-level default
engine = create_engine(
"postgresql://...",
isolation_level="REPEATABLE READ" # Default for all sessions
)
# Per-session isolation
async with async_session_factory() as session:
await session.connection(
execution_options={"isolation_level": "SERIALIZABLE"}
)
# This session uses SERIALIZABLE isolation
# Transaction-level in raw SQL
async with async_session_factory() as session:
await session.execute(text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"))
# Perform operations
await session.commit()Isolation Level Reference
| Level | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible |
| READ COMMITTED | No | Possible | Possible |
| REPEATABLE READ | No | No | Possible* |
| SERIALIZABLE | No | No | No |
*PostgreSQL prevents phantoms in REPEATABLE READ
Optimistic Locking
from sqlalchemy import Column, Integer
from sqlalchemy.orm import Mapped, mapped_column
class Account(Base):
__tablename__ = "accounts"
id: Mapped[int] = mapped_column(primary_key=True)
balance: Mapped[int]
version: Mapped[int] = mapped_column(default=0)
__mapper_args__ = {"version_id_col": version}
async def transfer_funds(from_id: int, to_id: int, amount: int):
"""Transfer with optimistic locking."""
async with async_session_factory() as session:
from_account = await session.get(Account, from_id)
to_account = await session.get(Account, to_id)
if from_account.balance < amount:
raise InsufficientFunds()
from_account.balance -= amount
to_account.balance += amount
try:
await session.commit()
except StaleDataError:
# Concurrent modification detected
await session.rollback()
raise ConcurrentModificationError()Pessimistic Locking
from sqlalchemy import select
async def transfer_with_lock(from_id: int, to_id: int, amount: int):
"""Transfer with row-level lock."""
async with async_session_factory() as session:
async with session.begin():
# Lock rows for update
stmt = (
select(Account)
.where(Account.id.in_([from_id, to_id]))
.with_for_update() # SELECT ... FOR UPDATE
)
result = await session.execute(stmt)
accounts = {a.id: a for a in result.scalars()}
from_account = accounts[from_id]
to_account = accounts[to_id]
if from_account.balance < amount:
raise InsufficientFunds()
from_account.balance -= amount
to_account.balance += amount
# Commit releases locks
# Lock with options
stmt = select(Account).with_for_update(
nowait=True, # Fail immediately if locked
skip_locked=True # Skip locked rows (for queue processing)
)Retry on Serialization Failure
from sqlalchemy.exc import OperationalError
import asyncio
async def retry_on_conflict(
func,
max_retries: int = 3,
base_delay: float = 0.1,
):
"""Retry transaction on serialization failure."""
for attempt in range(max_retries):
try:
return await func()
except OperationalError as e:
if "serialization" in str(e).lower() or "deadlock" in str(e).lower():
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt)
await asyncio.sleep(delay)
continue
raise
# Usage
async def process_order(order_id: int):
async def _process():
async with async_session_factory() as session:
async with session.begin():
order = await session.get(Order, order_id)
order.status = "processed"
await session.commit()
await retry_on_conflict(_process)Quick Reference
| Pattern | Use Case |
|---|---|
session.begin() | Auto-commit/rollback |
session.begin_nested() | Savepoint (partial rollback) |
with_for_update() | Row-level locking |
version_id_col | Optimistic concurrency |
| Isolation levels | Control visibility |
| Isolation Level | When to Use |
|---|---|
| READ COMMITTED | Default, most apps |
| REPEATABLE READ | Reports, analytics |
| SERIALIZABLE | Financial, inventory |
{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-01-16T14:06:30.874Z",
"slug": "0xdarkmatter-python-database-patterns",
"source_url": "https://github.com/0xDarkMatter/claude-mods/tree/main/skills/python-database-patterns",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "08f9de3f2eab4deb16e7529a2dd41f0b5d26f60267b8c27b046edacd2ad46b9b",
"tree_hash": "b234d57d21e23b18285f58a7e4aa8d7a0c4d2950b515fd2730e39b7ed254e9a9"
},
"skill": {
"name": "python-database-patterns",
"description": "SQLAlchemy and database patterns for Python. Triggers on: sqlalchemy, database, orm, migration, alembic, async database, connection pool, repository pattern, unit of work.",
"summary": "SQLAlchemy and database patterns for Python. Triggers on: sqlalchemy, database, orm, migration, alem...",
"icon": "🗄️",
"version": "1.0.0",
"author": "0xDarkMatter",
"license": "MIT",
"category": "data",
"tags": [
"SQLAlchemy",
"database",
"Python",
"ORM"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": [
"external_commands",
"env_access",
"scripts",
"network"
]
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "Documentation-only skill containing educational SQLAlchemy patterns. No executable code, no network calls, no credential access, no malicious patterns detected. Static scanner generated false positives by misidentifying markdown code blocks as executable code.",
"risk_factor_evidence": [
{
"factor": "external_commands",
"evidence": [
{
"file": "references/connection-pooling.md",
"line_start": 7,
"line_end": 42
},
{
"file": "references/connection-pooling.md",
"line_start": 42,
"line_end": 46
},
{
"file": "references/connection-pooling.md",
"line_start": 46,
"line_end": 78
},
{
"file": "references/connection-pooling.md",
"line_start": 78,
"line_end": 82
},
{
"file": "references/connection-pooling.md",
"line_start": 82,
"line_end": 121
},
{
"file": "references/connection-pooling.md",
"line_start": 121,
"line_end": 125
},
{
"file": "references/connection-pooling.md",
"line_start": 125,
"line_end": 163
},
{
"file": "references/connection-pooling.md",
"line_start": 163,
"line_end": 167
},
{
"file": "references/connection-pooling.md",
"line_start": 167,
"line_end": 217
},
{
"file": "references/connection-pooling.md",
"line_start": 217,
"line_end": 221
},
{
"file": "references/connection-pooling.md",
"line_start": 221,
"line_end": 263
},
{
"file": "references/connection-pooling.md",
"line_start": 263,
"line_end": 267
},
{
"file": "references/connection-pooling.md",
"line_start": 267,
"line_end": 280
},
{
"file": "references/connection-pooling.md",
"line_start": 280,
"line_end": 286
},
{
"file": "references/connection-pooling.md",
"line_start": 286,
"line_end": 287
},
{
"file": "references/connection-pooling.md",
"line_start": 287,
"line_end": 288
},
{
"file": "references/connection-pooling.md",
"line_start": 288,
"line_end": 289
},
{
"file": "references/connection-pooling.md",
"line_start": 289,
"line_end": 290
},
{
"file": "references/migrations.md",
"line_start": 7,
"line_end": 16
},
{
"file": "references/migrations.md",
"line_start": 16,
"line_end": 20
},
{
"file": "references/migrations.md",
"line_start": 20,
"line_end": 79
},
{
"file": "references/migrations.md",
"line_start": 79,
"line_end": 83
},
{
"file": "references/migrations.md",
"line_start": 83,
"line_end": 104
},
{
"file": "references/migrations.md",
"line_start": 104,
"line_end": 108
},
{
"file": "references/migrations.md",
"line_start": 108,
"line_end": 140
},
{
"file": "references/migrations.md",
"line_start": 140,
"line_end": 144
},
{
"file": "references/migrations.md",
"line_start": 144,
"line_end": 191
},
{
"file": "references/migrations.md",
"line_start": 191,
"line_end": 195
},
{
"file": "references/migrations.md",
"line_start": 195,
"line_end": 217
},
{
"file": "references/migrations.md",
"line_start": 217,
"line_end": 221
},
{
"file": "references/migrations.md",
"line_start": 221,
"line_end": 244
},
{
"file": "references/migrations.md",
"line_start": 244,
"line_end": 248
},
{
"file": "references/migrations.md",
"line_start": 248,
"line_end": 271
},
{
"file": "references/migrations.md",
"line_start": 271,
"line_end": 275
},
{
"file": "references/migrations.md",
"line_start": 275,
"line_end": 285
},
{
"file": "references/migrations.md",
"line_start": 285,
"line_end": 287
},
{
"file": "references/migrations.md",
"line_start": 287,
"line_end": 291
},
{
"file": "references/migrations.md",
"line_start": 291,
"line_end": 295
},
{
"file": "references/migrations.md",
"line_start": 295,
"line_end": 322
},
{
"file": "references/migrations.md",
"line_start": 322,
"line_end": 328
},
{
"file": "references/migrations.md",
"line_start": 328,
"line_end": 329
},
{
"file": "references/migrations.md",
"line_start": 329,
"line_end": 330
},
{
"file": "references/migrations.md",
"line_start": 330,
"line_end": 331
},
{
"file": "references/migrations.md",
"line_start": 331,
"line_end": 332
},
{
"file": "references/migrations.md",
"line_start": 332,
"line_end": 336
},
{
"file": "references/migrations.md",
"line_start": 336,
"line_end": 337
},
{
"file": "references/migrations.md",
"line_start": 337,
"line_end": 338
},
{
"file": "references/migrations.md",
"line_start": 338,
"line_end": 339
},
{
"file": "references/migrations.md",
"line_start": 339,
"line_end": 340
},
{
"file": "references/migrations.md",
"line_start": 340,
"line_end": 341
},
{
"file": "references/migrations.md",
"line_start": 341,
"line_end": 342
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 7,
"line_end": 38
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 38,
"line_end": 42
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 42,
"line_end": 83
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 83,
"line_end": 87
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 87,
"line_end": 117
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 117,
"line_end": 121
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 121,
"line_end": 144
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 144,
"line_end": 148
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 148,
"line_end": 198
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 198,
"line_end": 202
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 202,
"line_end": 224
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 224,
"line_end": 228
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 228,
"line_end": 252
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 252,
"line_end": 256
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 256,
"line_end": 284
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 284,
"line_end": 290
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 290,
"line_end": 291
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 291,
"line_end": 292
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 292,
"line_end": 293
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 293,
"line_end": 294
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 294,
"line_end": 295
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 295,
"line_end": 296
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 296,
"line_end": 297
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 297,
"line_end": 298
},
{
"file": "references/sqlalchemy-async.md",
"line_start": 298,
"line_end": 299
},
{
"file": "references/transactions.md",
"line_start": 7,
"line_end": 38
},
{
"file": "references/transactions.md",
"line_start": 38,
"line_end": 42
},
{
"file": "references/transactions.md",
"line_start": 42,
"line_end": 67
},
{
"file": "references/transactions.md",
"line_start": 67,
"line_end": 71
},
{
"file": "references/transactions.md",
"line_start": 71,
"line_end": 123
},
{
"file": "references/transactions.md",
"line_start": 123,
"line_end": 127
},
{
"file": "references/transactions.md",
"line_start": 127,
"line_end": 151
},
{
"file": "references/transactions.md",
"line_start": 151,
"line_end": 166
},
{
"file": "references/transactions.md",
"line_start": 166,
"line_end": 198
},
{
"file": "references/transactions.md",
"line_start": 198,
"line_end": 202
},
{
"file": "references/transactions.md",
"line_start": 202,
"line_end": 234
},
{
"file": "references/transactions.md",
"line_start": 234,
"line_end": 238
},
{
"file": "references/transactions.md",
"line_start": 238,
"line_end": 270
},
{
"file": "references/transactions.md",
"line_start": 270,
"line_end": 276
},
{
"file": "references/transactions.md",
"line_start": 276,
"line_end": 277
},
{
"file": "references/transactions.md",
"line_start": 277,
"line_end": 278
},
{
"file": "references/transactions.md",
"line_start": 278,
"line_end": 279
},
{
"file": "SKILL.md",
"line_start": 16,
"line_end": 39
},
{
"file": "SKILL.md",
"line_start": 39,
"line_end": 43
},
{
"file": "SKILL.md",
"line_start": 43,
"line_end": 66
},
{
"file": "SKILL.md",
"line_start": 66,
"line_end": 70
},
{
"file": "SKILL.md",
"line_start": 70,
"line_end": 92
},
{
"file": "SKILL.md",
"line_start": 92,
"line_end": 96
},
{
"file": "SKILL.md",
"line_start": 96,
"line_end": 127
},
{
"file": "SKILL.md",
"line_start": 127,
"line_end": 131
},
{
"file": "SKILL.md",
"line_start": 131,
"line_end": 149
},
{
"file": "SKILL.md",
"line_start": 149,
"line_end": 155
},
{
"file": "SKILL.md",
"line_start": 155,
"line_end": 156
},
{
"file": "SKILL.md",
"line_start": 156,
"line_end": 157
},
{
"file": "SKILL.md",
"line_start": 157,
"line_end": 158
},
{
"file": "SKILL.md",
"line_start": 158,
"line_end": 159
},
{
"file": "SKILL.md",
"line_start": 159,
"line_end": 160
},
{
"file": "SKILL.md",
"line_start": 160,
"line_end": 161
},
{
"file": "SKILL.md",
"line_start": 161,
"line_end": 165
},
{
"file": "SKILL.md",
"line_start": 165,
"line_end": 166
},
{
"file": "SKILL.md",
"line_start": 166,
"line_end": 167
},
{
"file": "SKILL.md",
"line_start": 167,
"line_end": 168
},
{
"file": "SKILL.md",
"line_start": 168,
"line_end": 172
},
{
"file": "SKILL.md",
"line_start": 172,
"line_end": 179
},
{
"file": "SKILL.md",
"line_start": 179,
"line_end": 180
},
{
"file": "SKILL.md",
"line_start": 180,
"line_end": 183
},
{
"file": "SKILL.md",
"line_start": 183,
"line_end": 184
}
]
},
{
"factor": "env_access",
"evidence": [
{
"file": "references/connection-pooling.md",
"line_start": 68,
"line_end": 68
},
{
"file": "references/connection-pooling.md",
"line_start": 68,
"line_end": 68
},
{
"file": "references/migrations.md",
"line_start": 41,
"line_end": 41
},
{
"file": "references/migrations.md",
"line_start": 61,
"line_end": 61
},
{
"file": "references/migrations.md",
"line_start": 34,
"line_end": 34
}
]
},
{
"factor": "scripts",
"evidence": [
{
"file": "references/sqlalchemy-async.md",
"line_start": 8,
"line_end": 13
},
{
"file": "SKILL.md",
"line_start": 44,
"line_end": 48
}
]
},
{
"factor": "network",
"evidence": [
{
"file": "skill-report.json",
"line_start": 6,
"line_end": 6
}
]
}
],
"critical_findings": [],
"high_findings": [],
"medium_findings": [],
"low_findings": [],
"dangerous_patterns": [],
"files_scanned": 7,
"total_lines": 1690,
"audit_model": "claude",
"audited_at": "2026-01-16T14:06:30.874Z"
},
"content": {
"user_title": "Implement Python database patterns with SQLAlchemy",
"value_statement": "Writing database code is complex and error-prone. This skill provides battle-tested SQLAlchemy 2.0 patterns for models, queries, async sessions, transactions, and migrations.",
"seo_keywords": [
"SQLAlchemy",
"Python database patterns",
"database ORM",
"async database",
"Alembic migrations",
"connection pooling",
"repository pattern",
"unit of work",
"Claude",
"Claude Code"
],
"actual_capabilities": [
"Define SQLAlchemy 2.0 models with mapped columns and type annotations",
"Write queries using select, where, joins, and aggregates",
"Configure async sessions with asyncpg and aiosqlite",
"Implement transaction patterns with savepoints and isolation levels",
"Set up Alembic migrations for schema changes",
"Configure connection pooling for production workloads"
],
"limitations": [
"Does not generate complete database layers or model files",
"Does not create database drivers or custom connectors",
"Does not provide deployment or infrastructure code",
"Requires existing knowledge of SQL fundamentals"
],
"use_cases": [
{
"target_user": "Python developers new to SQLAlchemy",
"title": "Learn SQLAlchemy 2.0",
"description": "Get started with modern SQLAlchemy patterns for declarative models and type-safe queries"
},
{
"target_user": "Backend engineers",
"title": "Build async API backends",
"description": "Implement FastAPI endpoints with async database sessions and transaction management"
},
{
"target_user": "DevOps and platform engineers",
"title": "Manage database schemas",
"description": "Set up Alembic migrations and configure connection pools for production databases"
}
],
"prompt_templates": [
{
"title": "Define a model",
"scenario": "Create SQLAlchemy model",
"prompt": "Create a SQLAlchemy 2.0 model called Product with id, name, price, and category relationship"
},
{
"title": "Write async query",
"scenario": "Query with async session",
"prompt": "Write an async function to fetch a user by email with their posts eagerly loaded"
},
{
"title": "Handle transaction",
"scenario": "Transfer funds atomically",
"prompt": "Implement a transfer_funds function with pessimistic locking to prevent race conditions"
},
{
"title": "Configure pool",
"scenario": "Production pool settings",
"prompt": "Show me how to configure connection pool size, overflow, and pre-ping for a production PostgreSQL async engine"
}
],
"output_examples": [
{
"input": "Create a User model with email unique constraint and a relationship to Post",
"output": [
"- Define User class with mapped columns",
"- Add unique constraint on email field",
"- Create one-to-many relationship to Post model",
"- Use back_populates for bidirectional navigation"
]
},
{
"input": "How do I set up async SQLAlchemy with FastAPI?",
"output": [
"- Create async engine with create_async_engine",
"- Configure async_sessionmaker with expire_on_commit=False",
"- Use FastAPI Depends for per-request session lifecycle",
"- Implement proper commit and rollback handling"
]
},
{
"input": "Configure connection pooling for high traffic",
"output": [
"- Set pool_size based on CPU cores and expected concurrency",
"- Configure max_overflow for traffic spikes",
"- Enable pool_pre_ping to detect stale connections",
"- Set pool_recycle to prevent timeout issues"
]
}
],
"best_practices": [
"Use SQLAlchemy 2.0 declarative style with Mapped and mapped_column for type safety",
"Prefer session.execute with select() over legacy session.query() method",
"Configure pool_pre_ping=True to catch stale connections before queries",
"Use eager loading (selectinload/joinedload) to avoid N+1 queries in async contexts"
],
"anti_patterns": [
"Using lazy loading in async sessions causes MissingGreenlet errors",
"Calling session.commit() inside session.begin() creates nested transaction issues",
"Hardcoding database URLs instead of using environment configuration",
"Skipping pool_recycle causing connections to timeout after long queries"
],
"faq": [
{
"question": "What SQLAlchemy versions are supported?",
"answer": "SQLAlchemy 2.0+ with Python 3.10+. Async requires asyncpg for PostgreSQL or aiosqlite for SQLite."
},
{
"question": "Can I use this with FastAPI?",
"answer": "Yes. The skill includes FastAPI dependency injection patterns for async database sessions."
},
{
"question": "How do I size the connection pool?",
"answer": "Start with pool_size=5 and max_overflow=10. Adjust based on concurrent users and query patterns."
},
{
"question": "Is my data safe with these patterns?",
"answer": "Yes. Patterns include transaction safety, optimistic locking, and proper session lifecycle management."
},
{
"question": "Why are my async queries failing?",
"answer": "Common causes include lazy loading without eager loading, uncommitted transactions, or connection pool exhaustion."
},
{
"question": "How does this compare to raw SQL?",
"answer": "SQLAlchemy provides type safety and abstraction while generating optimized SQL. Use text() for raw queries when needed."
}
]
},
"file_structure": [
{
"name": "assets",
"type": "dir",
"path": "assets",
"children": [
{
"name": "alembic.ini.template",
"type": "file",
"path": "assets/alembic.ini.template",
"lines": 60
}
]
},
{
"name": "references",
"type": "dir",
"path": "references",
"children": [
{
"name": "connection-pooling.md",
"type": "file",
"path": "references/connection-pooling.md",
"lines": 298
},
{
"name": "migrations.md",
"type": "file",
"path": "references/migrations.md",
"lines": 343
},
{
"name": "sqlalchemy-async.md",
"type": "file",
"path": "references/sqlalchemy-async.md",
"lines": 300
},
{
"name": "transactions.md",
"type": "file",
"path": "references/transactions.md",
"lines": 287
}
]
},
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 185
}
]
}
Related skills
FAQ
Which SQLAlchemy version does this cover?
SQLAlchemy 2.0 with the modern select() and Mapped[] declarative style.
Does it support async?
Yes, it covers async engines and sessions using asyncpg for PostgreSQL or aiosqlite.