Гайды
SQLAlchemy: работа с БД в Python
DeclarativeBase 2.0, select и сессии, relationship и загрузчики, async с asyncpg, Core и транзакции.
~12 мин чтения
SQLAlchemy: работа с БД в Python
SQLAlchemy 2.0 — единый стиль Core (SQL-выражения) и ORM (модели, сессии). Асинхронный стек с asyncpg хорошо сочетается с FastAPI — см. Асинхронные эндпоинты и asyncpg. Миграции — Alembic; Django ORM — оптимизация запросов в Django.
1. Движок и сессия (sync)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
class Base(DeclarativeBase):
pass
engine = create_engine("postgresql+psycopg://user:pass@localhost/db", echo=False)
Session = sessionmaker(bind=engine, autoflush=False, autocommit=False)
def get_session():
s = Session()
try:
yield s
finally:
s.close()
Для FastAPI оборачивают в Depends — см. asyncpg и официальный раздел.
2. Модель
from sqlalchemy import String, Integer
from sqlalchemy.orm import Mapped, mapped_column
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
name: Mapped[str] = mapped_column(String(100))
Mapped[] + mapped_column — стиль 2.0.
3. Запросы ORM
from sqlalchemy import select
with Session() as session:
u = session.scalars(select(User).where(User.email == "a@b.c")).one_or_none()
session.add(User(email="x@y.z", name="Ann"))
session.commit()
select() из sqlalchemy.
4. Отношения
from __future__ import annotations
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, selectinload
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(255))
orders: Mapped[list["Order"]] = relationship(back_populates="user")
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
user: Mapped[User] = relationship(back_populates="orders")
selectinload, joinedload — аналоги prefetch_related / select_related.
select(User).options(selectinload(User.orders))
5. Async (кратко)
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
engine = create_async_engine("postgresql+asyncpg://...")
AsyncSessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with AsyncSessionLocal() as session:
await session.execute(select(User).where(User.id == 1))
await session.commit()
6. Core: текст и безопасность
from sqlalchemy import text
session.execute(text("SELECT 1 WHERE id = :id"), {"id": x})
Параметры только через bind, не через f-string.
7. Транзакции
with Session() as session:
with session.begin():
session.add(...)
session.execute(...)
При исключении — откат.
8. Обновления и конфликты
session.merge(obj) удобен при десериализации DTO с частично известным PK; помните про отдельный SELECT и гонки — для высокой конкуренции чаще UPDATE ... WHERE через Core или версионирование строки (xmin/version_id). Удаление каскадом настраивается в relationship(..., cascade="all, delete-orphan") — проверьте, что не рвёте связи случайно в админских скриптах.
9. После commit: expire_on_commit
По умолчанию после commit() атрибуты экземпляра ORM помечаются как expired; при следующем обращении к полю SQLAlchemy может выполнить implicit lazy load — если сессия уже закрыта, получите ошибку или N+1 в неожиданном месте. Для ответов API либо sessionmaker(..., expire_on_commit=False) (осознанно: «устаревшие» данные в памяти до явного refresh), либо model_validate в Pydantic / dict до закрытия сессии.
10. Чек-лист
- Один стиль 2.0 (
select,Mapped). - Явные
relationshipи стратегии загрузки для списков. - Пул и
pool_pre_ping=Trueдля продакшена. - Политика
expire_on_commitсогласована с тем, как вы отдаёте объекты из эндпоинтов. - Alembic для изменения схемы — гайд.
Дальше: Flask + SQLAlchemy · тег SQLAlchemy