Модели SQLAlchemy — проектирование таблиц базы данных

В этой главе вы научитесь определять модели данных с использованием нативного ORM SQLAlchemy и управлять сессиями базы данных через внедрение зависимостей FastAPI.


Почему в FastAPI нет встроенной ORM?

В Django есть встроенная ORM, а Flask рекомендует Flask-SQLAlchemy.

В FastAPI нет встроенной ORM — он хочет, чтобы вы выбрали наиболее подходящий инструмент,SQLAlchemy + Pydantic— лучшее сочетание, признанное сообществом.

(venv) $ pip install sqlalchemy

Настройте подключение к базе данных

Пример

# Путь к файлу: database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

# Путь к файлу базы данных SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///./blog.db"

# Создание движка: check_same_thread — параметр, специфичный для SQLite
engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    connect_args={"check_same_thread": False}   # Специфично для SQLite: разрешить кросс-поточность
)

# Создание фабрики сессий
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Базовый класс: все ORM-модели наследуются от него
class Base(DeclarativeBase):
    pass

check_same_thread=False— это необходимая конфигурация для SQLite в FastAPI. По умолчанию SQLite не позволяет использовать одно и то же соединение в разных потоках, но в веб-приложениях каждый запрос может обрабатываться в отдельном потоке.

Внедрение зависимостей: get_db

Пример

# Путь к файлу: добавить в database.py
from fastapi import Depends

def get_db():
    """Внедрение зависимостей FastAPI: создать независимую сессию базы данных для каждого запроса"""
    db = SessionLocal()
    try:
        yield db          # Используйте эту сессию во время запроса
    finally:
        db.close()        # Автоматически закрывать сессию после завершения запроса, чтобы предотвратить утечки соединений

Depends(get_db)— это система внедрения зависимостей FastAPI, в которой функции маршрутов автоматически получают сессии базы данных через внедрение зависимостей.


Определите модели: Post и Category

Пример

# Путь к файлу: models.py
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from database import Base

class Category(Base):
    """Категория статьи"""
    __tablename__ = "categories"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(50), unique=True, nullable=False)
    slug = Column(String(50), unique=True, nullable=False)
    # back_populates двунаправленная связь
    posts = relationship("Post", back_populates="category")


class Post(Base):
    """Запись блога"""
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), nullable=False)
    slug = Column(String(200), unique=True, nullable=False)
    summary = Column(Text, default="")
    content = Column(Text, nullable=False)
    category_id = Column(Integer, ForeignKey("categories.id"), nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    # Двунаправленная связь с Category
    category = relationship("Category", back_populates="posts")

В FastAPI используетсянативный SQLAlchemy(не из Flask-SQLAlchemydb.Model). Обратите внимание на различия в импортах Column, наследовании Base и синтаксисе relationship. Таблица сравнения будет предоставлена позже.


Инициализировать таблицы базы данных

Пример

# Путь к файлу: создать таблицу в событии startup в main.py
from fastapi import FastAPI
from database import engine, Base
from models import Category, Post   # Убедитесь, что классы моделей импортированы, чтобы Base.metadata мог их распознать

app = FastAPI()

@app.on_event("startup")
def on_startup():
    """Автоматически создаёт таблицы базы данных при запуске приложения (только для разработки)"""
    Base.metadata.create_all(bind=engine)

Позже мы переключимся на миграции Alembic (глава 6),create_allИспользуется только на этапе быстрого прототипирования.


Вставка тестовых данных

Вставка тестовых данных в скрипте Python:

Пример

# Путь к файлу: seed.py (запустить только один раз)
from database import SessionLocal
from models import Category, Post

db = SessionLocal()

# Создать Category
py = Category(name="Python", slug="python")
css = Category(name="CSS", slug="css")
fastapi = Category(name="FastAPI", slug="fastapi")
db.add_all([py, css, fastapi])
db.commit()

# Создать статью
db.add_all([
    Post(title="Полное руководство для начинающих по FastAPI", slug="fastapi-guide",
         summary="Изучите FastAPI с нуля", content="<h2>Почему стоит выбрать FastAPI?</h2><p>...</p>",
         category_id=fastapi.id),
    Post(title="Корутины Python: объяснение", slug="python-coroutine",
         summary="Разберитесь в asyncio", content=<h2>Что такое корутина?</h2><p>...</p>,
         category_id=py.id),
    Post(title="CSS Grid Layout в действии", slug="css-grid",
         summary="Реализация адаптивного макета с помощью Grid", content="<h2>Введение в Grid</h2><p>...</p>",
         category_id=css.id),
])
db.commit()
db.close()
print("Вставка тестовых данных завершена!")

Запуститеpython seed.py, вставьте 3 статьи в базу данных.


Сравнение синтаксиса ORM в четырёх фреймворках.

ДействиеDjangoFlask-SQLAlchemyFastAPI (нативный SQLAlchemy)
базовый классmodels.Modeldb.ModelBase (DeclarativeBase)
Fieldsmodels.CharField()db.Column(...)Column(...)
Внешний ключForeignKey(to, on_delete)db.ForeignKey('table.col')ForeignKey('table.col')
СвязьАвтоматическое обратное отношениеdb.relationship + backrefrelationship + back_populates
СессияАвтоматическое управлениеdb.sessionSessionLocal() + Depends

Итоги главы

В этой главе вы освоили интеграцию SQLAlchemy в FastAPI: create_engine + SessionLocal для настройки подключения к базе данных, DeclarativeBase для определения базового класса модели, внедрение зависимости get_db для управления сессиями, а также Column/relationship для определения полей и связей.

По сравнению с Flask-SQLAlchemy, нативный SQLAlchemy требует немного больше кода, но полностью открыт и контролируем.

Другие расширения