1. Введение в SQLAlchemy и ORM
SQLAlchemy — это мощная библиотека на языке Python, предназначенная для работы с базами данных с использованием SQL и программирования объектно-ориентированных запросов.
Она предоставляет полный набор инструментов для реализации моделей данных, выполнения запросов и анализа результатов в Python-приложениях.
SQLAlchemy предлагает два подхода к работе с базами данных: ORM (Object-Relational Mapping) и Core.
2. ORM (Object-Relational Mapping)
ORM — это техника программирования, которая позволяет использовать объектно-ориентированный подход к работе с данными в базе данных. Она предоставляет возможность отображать записи базы данных на объекты в коде программы и наоборот.
Основные принципы ORM
- Отображение объектов: ORM позволяет отображать таблицы базы данных на классы объектов в программе. Каждая строка таблицы становится экземпляром класса, а столбцы таблицы — атрибутами объекта.
- Отображение связей: ORM позволяет отображать связи между таблицами базы данных на отношения между объектами (связь «один-к-одному», «один-ко-многим» или «многие-ко-многим»).
- Абстрагирование от SQL: ORM позволяет работать с данными, не прибегая к написанию SQL-запросов. Разработчик использует объектно-ориентированный интерфейс для выполнения операций с данными.
- Поддержка различных баз данных: ORM обеспечивает абстракцию над конкретной СУБД, что позволяет использовать один и тот же код с разными типами баз данных.
Преимущества ORM
- Абстракция: работа с данными как с объектами Python, что упрощает разработку.
- Лёгкость в использовании: более высокий уровень абстракции делает код более понятным.
- Гибкость: возможность использовать наследование, миксины и другие возможности ООП.
Недостатки ORM
- Производительность: может быть медленнее Core для сложных запросов из-за дополнительной абстракции.
- Сложность: некоторые сложные SQL-запросы могут быть трудно реализовать через ORM.
3. Core
Core предоставляет более низкоуровневый доступ к базе данных, позволяя работать непосредственно с SQL-запросами и таблицами. Это даёт больше контроля и гибкости при работе с базой данных.
Преимущества Core
- Производительность: может быть быстрее ORM для сложных запросов благодаря прямой работе с SQL.
- Гибкость: позволяет точно контролировать выполнение запросов и структуру данных.
Недостатки Core
- Сложность: требует глубоких знаний SQL и может сделать код менее читаемым.
- Время разработки: разработка может занять больше времени из-за необходимости писать более сложный код.
4. Выбор подхода
Выбор между ORM и Core зависит от множества факторов, включая сложность проекта, требования к производительности и предпочтения разработчика.
- Если проект включает в себя сложную логику работы с данными и требует высокой производительности, Core может быть предпочтительнее.
- Для простых приложений или приложений, где скорость разработки является ключевым фактором, ORM может быть более подходящим выбором.
5. Установка SQLAlchemy
Для начала работы с SQLAlchemy необходимо установить саму библиотеку с помощью pip:
pip install SQLAlchemy
6. Конфигурация подключения
Так же как и при подключении к СУБД через интерфейс СУБД (например, Workbench), необходимо указать параметры подключения. Шаблон строки подключения:
'<DBMS>+<library>://<user>:<password>@<host>:<port>/<database>'
<DBMS>+<library>— к какой СУБД будет подключение и через какую библиотеку<user>— имя пользователя<password>— пароль для указанного пользователя<host>— адрес сервера, к которому вы хотите подключиться<port>— номер порта, на котором работает СУБД (например, для MySQL стандартный порт: 3306)<database>— название конкретной базы данных, к которой вы хотите получить доступ
Пример строки подключения к MySQL:
'mysql+pymysql://user:password@localhost:3306/mydatabase'
7. Создание движка (create_engine)
Для создания движка используется функция create_engine(), которая требует настройки через строку подключения и может принимать дополнительные параметры, настраивающие его поведение. Движок SQLAlchemy служит фундаментом для взаимодействия с базой данных, управляя подключениями, сессиями, транзакциями и выполнением SQL-запросов.
Примеры создания движка с разными СУБД:
from sqlalchemy import create_engine
# PostgreSQL
engine = create_engine('postgresql://user:password@localhost:5432/mydatabase')
# MySQL (требует pip install pymysql)
engine = create_engine('mysql+pymysql://user:password@localhost:3306/mydatabase')
# SQLite (для локального файла)
engine = create_engine('sqlite:///path/to/database.db')
# SQLite (в оперативной памяти — данные не сохраняются)
engine = create_engine('sqlite:///:memory:')
pip install pymysql
8. Сессия SQLAlchemy
Жизненный цикл сессии
- Открытие сессии: сессия начинается с создания экземпляра Session.
- Работа с объектами: в сессию могут быть добавлены новые объекты, существующие объекты могут быть изменены или удалены.
- Фиксация изменений: изменения фиксируются в базе данных с помощью метода
commit(). - Закрытие сессии: сессия может быть закрыта методом
close(). После закрытия сессии объекты остаются в памяти, но отсоединяются от базы данных.
Состояния объекта в сессии
- Transient: объект создан, но не связан с сессией.
- Pending: объект связан с сессией, но ещё не сохранён в базу данных.
- Persistent: объект сохранён в базу данных и находится под управлением сессии.
- Detached: объект был связан с сессией, но сейчас отсоединён от неё.
- Deleted: объект был удалён из базы данных в рамках текущей сессии.
Создание сессии (стиль лекции — 1.x)
В лекции сессия создаётся через sessionmaker — фабрику сессий:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Создаём экземпляр движка SQLAlchemy
engine = create_engine('sqlite:///my_database.db')
# Создаём класс Session, который будет использоваться для взаимодействия с БД
Session = sessionmaker(bind=engine)
# Создаём экземпляр сессии
session = Session()
from sqlalchemy.orm import Session
with Session(engine) as session:
# работа с объектами
session.commit()
Подробнее об отличиях 1.x от 2.x — в разделе «Старый vs Новый».
Полный пример использования сессии
Базовое использование сессии для добавления объектов (пример из лекции, стиль 1.x):
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, declarative_base
Base = declarative_base()
engine = create_engine('sqlite:///example.db')
# Определяем класс User, который наследуется от базового класса Base.
# Этот класс представляет собой сущность базы данных.
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(30))
age = Column(Integer)
Session = sessionmaker(bind=engine)
session = Session()
# Для того чтобы в базе данных появилась таблица, вызываем метод create_all()
# объекта metadata базового класса Base.
# SQLAlchemy автоматически анализирует классы моделей данных и создаёт
# соответствующие таблицы в базе данных.
Base.metadata.create_all(engine)
# Создание нового пользователя
new_user = User(name="John Doe", age=30)
# Добавляем объект в сессию с помощью метода add().
session.add(new_user)
# Вызываем метод commit(), чтобы зафиксировать все изменения в базе данных.
session.commit()
DeclarativeBase, Mapped и mapped_column. Сравнение стилей — в разделе «Старый vs Новый».
9. Логирование событий
В контексте SQLAlchemy логирование включает запись сведений о выполнении SQL-запросов, ошибках, времени выполнения операций и другой диагностической информации. Это позволяет разработчикам отслеживать, что происходит в приложении, анализировать проблемы и оптимизировать производительность.
SQLAlchemy позволяет настраивать логирование через стандартный модуль logging в Python:
import logging
# Настройка базового логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('sqlalchemy.engine')
# Включение логирования SQL-запросов
logger.setLevel(logging.INFO)
Теперь SQLAlchemy будет выводить информацию о всех SQL-запросах и ответах. Эта конфигурация позволяет глубоко интегрироваться с процессом работы с базой данных, делая его максимально прозрачным и контролируемым для разработчика.
10. Практическая работа (задачи лекции)
В лекции даны две практические задачи:
- Задача 1: создайте экземпляр движка для подключения к SQLite базе данных в памяти.
- Задача 2: создайте сессию в продолжение к предыдущему коду.
Полные решения — в разделе «Задания» и «Решения».
Следующий раздел не является частью учебной лекции. Он добавлен для понимания полной картины инструментария SQLAlchemy. Код-примеры взяты из официального API SQLAlchemy 2.x.
Стили маппинга в SQLAlchemy 2.x
В лекции из всех вариантов ORM изучается только декларативный стиль (Declarative ORM). SQLAlchemy 2.x поддерживает пять стилей работы с моделями — от полного отказа от ORM до dataclass-удобства.
1. Core Table — без ORM-классов
Таблицы описываются напрямую через Table и MetaData. Никаких Python-классов — только структура таблиц и SQL-запросы. Доступ к столбцам через users.c.id.
from sqlalchemy import Table, Column, Integer, String, MetaData
metadata = MetaData()
users = Table(
"users", metadata,
Column("id", Integer, primary_key=True),
Column("name", String(30)),
)
Когда использовать: скрипты миграции, сложные SQL-запросы, ситуации, когда нужен полный контроль над SQL без ORM-объектов.
2. Declarative ORM — основной современный вариант ← изучается в лекции
Класс является одновременно Python-классом, ORM-моделью и описанием таблицы. Основной стиль SQLAlchemy 2.x. Лучший выбор для Flask и FastAPI-приложений.
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
Когда использовать: большинство современных проектов, Flask, FastAPI, любые новые приложения.
3. Imperative / Classical Mapping — класс и таблица отдельно
Обычный Python-класс (без наследования) связывается с таблицей через registry().map_imperatively(). Позволяет полностью отделить доменные классы от описания БД.
from sqlalchemy.orm import registry
mapper_registry = registry()
class User:
pass # обычный Python-класс
users_table = Table("users", mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(30)),
)
mapper_registry.map_imperatively(User, users_table)
Когда использовать: Domain-Driven Design (DDD), когда домен не должен «знать» о базе данных.
4. Hybrid Declarative — декларативный класс, внешняя таблица
Класс декларативный, но таблица описана отдельно и подключается через __table__.
class User(Base):
__table__ = users_table # таблица описана отдельно
Когда использовать: когда таблицы уже описаны через Core Table, но нужны ORM-возможности.
5. Dataclass Mapping — dataclass + ORM
Сочетает удобство dataclass (автогенерация __init__, __repr__) с возможностями ORM.
from sqlalchemy.orm import MappedAsDataclass, DeclarativeBase
class Base(MappedAsDataclass, DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, init=False)
name: Mapped[str]
Когда использовать: когда нужен dataclass-стиль инициализации объектов.
Сравнительная таблица стилей
| Стиль | Когда использовать |
|---|---|
| Declarative ORM ← учим в лекции | Основной выбор для большинства проектов (Flask, FastAPI) |
| Core Table | SQL-контроль без ORM-объектов; скрипты, миграции |
| Imperative / Classical | DDD; доменные классы не должны зависеть от БД |
| Hybrid Declarative | Таблицы уже описаны через Core, нужен ORM поверх |
| Dataclass Mapping | Dataclass-стиль инициализации + ORM |