Перейти к содержимому

ORM SQLAlchemy в Python: от основ до практики

ORM SQLAlchemy в Python: от основ до практики

ORM SQLAlchemy в Python

Подробное руководство: от настройки окружения до выполнения сложных SQL-запросов методами Python.

Навигация по курсу


Часть 1. Введение в ORM и SQLAlchemy

1.1 Что такое ORM?

Представьте, что у вас есть база данных (таблицы, строки, внешние ключи) и код на Python (классы, объекты, списки). Они говорят на разных языках. Чтобы сохранить объект Python в БД, вам пришлось бы вручную разбирать его на переменные и писать SQL-запрос `INSERT INTO...`.

ORM (Object-Relational Mapping) — это «переводчик», который делает это за вас. Она позволяет работать с таблицами базы данных так, будто это обычные объекты Python:

  • Строка в таблице превращается в экземпляр класса.
  • Столбец таблицы становится атрибутом этого класса.
  • SQL-запросы заменяются на вызов методов (например, session.add()).

1.2 Почему именно SQLAlchemy?

В экосистеме Python много ORM (например, встроенная в Django или Peewee), но SQLAlchemy — стандарт де-факто для серьезных проектов. Её главная особенность — гибкость. Она предлагает два уровня работы:

Core (Ядро)

Низкоуровневый инструмент. Вы пишете код на Python, который очень точно повторяет структуру SQL-запросов. Это дает полный контроль над базой данных.

ORM

Высокоуровневая надстройка. Вы описываете классы и связи между ними, а библиотека сама решает, как лучше построить SQL-запрос.

Часть 2. Подготовка окружения

2.1 Установка библиотек

SQLAlchemy сама по себе не умеет общаться с базой данных напрямую. Ей нужен посредник — драйвер. Для PostgreSQL самым популярным является psycopg2, но он требует компиляции C-библиотек. Для начала проще использовать pg8000 — он написан на чистом Python и работает везде.

Команда для терминала:
pip install sqlalchemy pg8000

2.2 Запуск базы данных (PostgreSQL)

Чтобы не устанавливать PostgreSQL локально в систему, удобнее всего запустить её в изолированном контейнере Docker. Это позволит создать чистую базу для экспериментов, которую легко удалить.

Пример файла docker-compose.yml:
version: '3'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: blog        # Имя базы данных
      POSTGRES_USER: user      # Имя пользователя
      POSTGRES_PASSWORD: example
    ports:
      - "5432:5432"            # Проброс порта наружу

Запустите контейнер командой docker-compose up -d. Теперь у вас есть работающая база данных на порту 5432.

Часть 3. SQLAlchemy Core: создание таблиц «вручную»

Этот раздел важен для понимания того, как SQLAlchemy работает "под капотом". В реальной жизни вы чаще будете использовать подход из Части 4.

3.1 Описание схемы данных

В подходе Core мы явно создаем объекты Table, описывая каждую колонку. Все таблицы должны быть зарегистрированы в специальном каталоге — объекте MetaData. Это позволяет SQLAlchemy знать всё о структуре нашей БД.

from sqlalchemy import MetaData, Table, Column, Integer, String

# 1. Создаем каталог метаданных
metadata = MetaData()

# 2. Описываем таблицу "authors"
authors_table = Table(
    "authors", metadata,                     # Имя таблицы и привязка к метаданным
    Column("id", Integer, primary_key=True), # Первичный ключ
    Column("username", String(32), nullable=False, unique=True),
    Column("email", String, nullable=True, unique=True)
)

3.2 Создание таблиц в базе

Теперь, когда мы описали структуру в Python, нужно отправить команду в базу данных, чтобы она создала физические таблицы. Для этого создается Engine (Движок) — это точка входа для любого взаимодействия с БД.

from sqlalchemy import create_engine

# Строка подключения: драйвер://пользователь:пароль@хост:порт/база
db_url = "postgresql+pg8000://user:example@localhost:5432/blog"

# echo=True выводит все SQL-запросы в консоль (полезно для обучения)
engine = create_engine(db_url, echo=True)

# Команда create_all смотрит в metadata и создает таблицы, которых еще нет
metadata.create_all(bind=engine)

⚠️ Важно: Метод create_all() безопасен — он не перезапишет существующие таблицы. Но он и не обновит их, если вы добавите новую колонку в код. Для изменений структуры нужны миграции (Alembic).

Часть 4. Декларативный стиль ORM (SQLAlchemy 2.0+)

Это основной способ работы в современных приложениях.

4.1 Базовый класс

Вместо того чтобы создавать таблицы и классы отдельно, мы используем Декларативный стиль. Мы создаем базовый класс, от которого будут наследоваться все наши модели. Этот класс автоматически собирает метаданные (MetaData).

from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String

# Создаем базовый класс для всех моделей
Base = declarative_base()

4.2 Определение модели User

Теперь создадим класс User. Благодаря наследованию от Base, этот класс одновременно является и описанием таблицы SQL, и обычным классом Python, который можно инстанцировать.

class User(Base):
    __tablename__ = "users"  # Явно указываем имя таблицы в БД

    # Атрибуты класса становятся колонками таблицы
    id = Column(Integer, primary_key=True)
    username = Column(String(32), nullable=False, unique=True)
    email = Column(String, nullable=True, unique=True)

    # Метод для красивого вывода объекта в консоль (опционально)
    def __repr__(self):
        return f"User(id={self.id}, username='{self.username}', email={self.email!r})"

4.3 Применение схемы

Как и в Core-подходе, нам нужно попросить движок создать таблицы, описанные в наследниках Base.

# Base.metadata содержит информацию о таблице users
Base.metadata.create_all(bind=engine)

Часть 5. Работа с данными через Session

5.1 Что такое Сессия?

Session — это "рабочая область" для ваших объектов. Вы не пишете данные в базу мгновенно. Сначала вы добавляете объекты в сессию (как товары в корзину), а затем выполняете commit (оплата на кассе), чтобы отправить все изменения в базу одной транзакцией.

from sqlalchemy.orm import sessionmaker, Session

# Фабрика для создания сессий
SessionLocal = sessionmaker(bind=engine)

# Использование через контекстный менеджер (рекомендуется)
# Это гарантирует, что сессия закроется даже при ошибках
with SessionLocal() as session:
    pass

5.2 Добавление данных (Create)

Чтобы создать запись, мы просто создаем экземпляр класса User и передаем его сессии. Обратите внимание: пока не вызван commit(), данные существуют только в памяти Python и транзакции БД, но не зафиксированы.

def create_user(session: Session, username: str, email: str = None):
    # 1. Создаем объект Python
    new_user = User(username=username, email=email)

    # 2. Добавляем в сессию ("в корзину")
    session.add(new_user)

    # 3. Фиксируем изменения (генерирует INSERT и выполняет COMMIT)
    session.commit()

    print(f"Пользователь сохранен с ID: {new_user.id}")
    return new_user

5.3 Массовая вставка

Если нужно добавить много объектов, используйте add_all(). SQLAlchemy оптимизирует это в один эффективный запрос.

users_list = [
    User(username="alice"),
    User(username="bob"),
    User(username="charlie")
]
session.add_all(users_list)
session.commit()

Часть 6. Чтение данных (Read)

В SQLAlchemy 2.0 унифицирован синтаксис запросов. Теперь мы строим запросы, используя функцию select(), очень похожую на обычный SQL.

6.1 Поиск по первичному ключу

Самый простой способ получить объект, если вы знаете его ID.

# session.get() — вернет None, если объект не найден
user = session.get(User, 1)
print(user)

6.2 Выборка с условиями (WHERE)

Для более сложных запросов используется конструкция select(Модель).where(Условие). Важно понимать разницу между методами исполнения:
scalar() — возвращает один объект (или None).
scalars().all() — возвращает список объектов.

from sqlalchemy import select

def find_user_by_name(session, name):
    # Строим запрос
    stmt = select(User).where(User.username == name)

    # Выполняем и получаем один результат
    return session.scalar(stmt)

# Пример с регистронезависимым поиском (ILIKE)
stmt = select(User).where(User.username.ilike("%john%"))
users = session.scalars(stmt).all()

6.3 Сложные фильтры (AND / OR)

Условия внутри where() по умолчанию объединяются через AND. Если нужно OR, импортируем функцию or_.

from sqlalchemy import or_

stmt = select(User).where(
    or_(
        User.email.is_not(None),       # Email заполнен
        User.username.ilike("%admin%") # ИЛИ имя содержит "admin"
    )
).order_by(User.id.desc())             # Сортировка

Часть 7. Обновление данных (Update)

7.1 "Pythonic" способ (объектный)

Это классический способ ORM. Мы загружаем объект в память, меняем его свойства (как у обычного объекта Python), а затем делаем коммит. SQLAlchemy автоматически отследит изменения (Dirty checking) и сгенерирует нужный UPDATE.

user = session.get(User, 1)  # 1. Загрузили
if user:
    user.email = "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."  # 2. Изменили
    session.commit()  # 3. Сохранили (SQL генерируется здесь)

7.2 Массовое обновление (SQL style)

Если нужно обновить тысячи записей, загружать их все в память объектов неэффективно. Лучше отправить один SQL-запрос UPDATE. Это делается через функцию update().

from sqlalchemy import update

stmt = (
    update(User)
    .where(User.username.ilike("%test%"))  # Кого обновляем?
    .values(email=None)                    # Что устанавливаем?
)
session.execute(stmt)
session.commit()

Этот метод работает намного быстрее для массовых операций, так как обходит создание Python-объектов.

Часть 8. Важные нюансы работы

Жизненный цикл объектов и Expire

У новичков часто возникает вопрос: "Почему после commit() SQLAlchemy снова делает SELECT, когда я обращаюсь к объекту?".

Это происходит потому, что после commit() сессия по умолчанию "протухает" (expire) все объекты. Это защита: ведь триггеры в базе данных или другие транзакции могли изменить данные в этот момент. SQLAlchemy гарантирует, что вы работаете со свежими данными.


Часть 9. Практические советы

Безопасность

Никогда не подставляйте переменные в строки запроса через f-строки! Используйте механизмы SQLAlchemy (например, where(User.name == name)) — они автоматически экранируют данные, защищая от SQL-инъекций.

Отладка

Включайте echo=True при создании движка на этапе разработки. Видеть реальный SQL, который генерирует ORM — лучший способ понять ошибки и оптимизировать запросы.


Часть 10. Структура проекта

Не пишите весь код в одном файле. Хорошая практика для Python-проекта с базой данных выглядит так:

my_project/
├── src/
│   ├── database.py     # Создание engine и SessionLocal
│   ├── models.py       # Объявление классов (User, Post и т.д.)
│   ├── crud.py         # Функции для бизнес-логики (create_user, get_users)
│   └── main.py         # Точка входа в приложение
├── .env                # Пароли и URL базы (не комитить в git!)
└── requirements.txt

Заключение

Вы освоили базу!

Мы прошли путь от установки драйверов до написания оптимизированных запросов. Теперь вы можете создавать схемы данных, безопасно управлять транзакциями и строить архитектуру приложений.

Воскресенье, 25 января 2026
ORM SQLAlchemy в Python: от основ до практики