Модели: таблицы как классы Python

Модель — это класс Python, описывающий таблицу: каждый атрибут становится столбцом. Один класс User — одна таблица users со столбцами id, name, email.
Модель — это схема таблицы на языке Python. В современном SQLAlchemy 2.x/Flask-SQLAlchemy 3.x столбцы объявляют типизированно через Mapped и mapped_column: тип столбца виден прямо в аннотации, а IDE подсказывает поля.

В прошлом уроке мы настроили db. Теперь опишем, что хранить. Модель наследует db.Model, а столбцы задаются современным типизированным синтаксисом: аннотация Mapped[тип] плюс mapped_column с параметрами.

from sqlalchemy.orm import Mapped, mapped_column
from .extensions import db

class User(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(db.String(80))
    email: Mapped[str] = mapped_column(db.String(120), unique=True)
    active: Mapped[bool] = mapped_column(default=True)

    def __repr__(self):
        return f"<User {self.name}>"

Разберём параметры: primary_key=True — уникальный идентификатор строки (обычно автоинкремент); unique=True — значение не повторяется (двух пользователей с одним email не будет); default — значение по умолчанию. Аннотация Mapped[str] говорит и Python, и SQLAlchemy, что столбец строковый и обязательный (а Mapped[str|None] — необязательный).

После описания моделей таблицы создаются один раз в контексте приложения:

with app.app_context():
    db.create_all()

Типизированный синтаксис Mapped/mapped_column — это современный стандарт SQLAlchemy 2.x, и он стоит того, чтобы привыкнуть к нему сразу: тип столбца виден прямо в аннотации, IDE подсказывает поля и ловит опечатки, а Mapped[str] против Mapped[str | None] явно различает обязательные и необязательные столбцы. Отдельно стоит усвоить ограничение db.create_all: он создаёт только отсутствующие таблицы и не трогает существующие. Как только модель меняется на работающем проекте с данными, create_all бессилен — нужны миграции. Flask-Migrate поверх Alembic генерирует и применяет пошаговые изменения схемы, сохраняя данные. На учебном проекте можно стартовать с create_all, но привычку к миграциям лучше выработать рано.

Как работает под капотом

SQLAlchemy читает класс и его аннотации и строит из них определение таблицы — фактически генерирует SQL вида CREATE TABLE. db.create_all обходит все модели и создаёт недостающие таблицы.

  class User:                  CREATE TABLE users (
    id   Mapped[int] PK   ──▶    id     INTEGER PRIMARY KEY,
    name Mapped[str]      ──▶    name   VARCHAR(80) NOT NULL,
    email unique         ──▶    email  VARCHAR(120) UNIQUE,
    active default=True  ──▶    active BOOLEAN DEFAULT 1
                               );

Смоделируем «класс → схема таблицы» обычным Python: соберём описание столбцов из определения модели.

class Column:
    def __init__(self, type_, primary_key=False, unique=False):
        self.type_ = type_
        self.primary_key = primary_key
        self.unique = unique

class User:
    columns = {
        "id": Column("INTEGER", primary_key=True),
        "name": Column("VARCHAR(80)"),
        "email": Column("VARCHAR(120)", unique=True),
    }

def create_table_sql(model, name):
    parts = []
    for col, c in model.columns.items():
        flags = " PRIMARY KEY" if c.primary_key else (" UNIQUE" if c.unique else "")
        parts.append(f"  {col} {c.type_}{flags}")
    return f"CREATE TABLE {name} (\n" + ",\n".join(parts) + "\n);"

print(create_table_sql(User, "users"))

Запусти: из описания класса собрался SQL создания таблицы. SQLAlchemy делает это автоматически для каждой модели при db.create_all.

Частые ошибки

  • Забыть primary_key. Каждой таблице нужен уникальный идентификатор строки.
  • Менять модель и ждать, что таблица обновится сама. db.create_all не меняет существующие таблицы — для изменений схемы нужны миграции (Alembic/Flask-Migrate).
  • create_all без app_context. Операции с базой требуют контекста приложения.

Best practices

  • Используй типизированный синтаксис Mapped/mapped_column — он современнее и дружит с IDE.
  • Для эволюции схемы подключай миграции (Flask-Migrate поверх Alembic), а не create_all.
  • Добавляй __repr__ — упрощает отладку.

Что запомнить

  • Модель — класс-описание таблицы; атрибуты становятся столбцами.
  • Современный синтаксис: Mapped[тип] + mapped_column с параметрами.
  • primary_key, unique, default задают ограничения и поведение столбца.
  • create_all создаёт новые таблицы; для изменений схемы нужны миграции.

Итог: модель — это класс-описание таблицы с типизированными столбцами через Mapped/mapped_column; db.create_all генерирует таблицы. Дальше научимся читать и писать данные через сессию.

Проверьте себя
1. Что задаёт mapped_column(primary_key=True)?
AШифрование столбца
BУникальный идентификатор строки в таблице
CЗначение по умолчанию
DИмя таблицы
2. Обновит ли db.create_all() уже существующую таблицу при изменении модели?
AДа, автоматически
BНет — он только создаёт недостающие таблицы; для изменений нужны миграции
CДа, но удалит данные
DТолько в SQLite