Как избежать дублирования моделей с помощью SQLModel

Вопрос: Как избежать дублирования моделей с помощью SQLModel

Линия поддержки
Линия поддержки Ответы на вопросы пользователей
8 апреля 2026

Объяснили, когда у Python‑сервиса в Kubernetes не хватает памяти, и как аккуратно выставить requests и limits, чтобы ничего не сломать.

Изображение записи

Комментарий пользователя

Добрый день, в FastAPI‑проекте у нас есть одна модель в Pydantic‑DTO, а вторая — почти та же самая, но в виде SQLAlchemy‑table. Поэтому мы все время поправляем их параллельно, и иногда между схемой БД и OpenAPI‑документацией накапливаются расхождения. Можно ли как‑то объединить все в один класс, чтобы не писать модели «дважды»?

Семен Дмитриев Пользователь

Ответ специалиста

Добрый день, Семен! Такую картину мы видим сплошь и рядом: одна модель в pydantic.BaseModel, вторая — в sqlalchemy declarative_base, и между ними куча дублирующих полей. 

SQLModel как раз создан ради этого — чтобы модель была одновременно и ORM‑таблицей, и Pydantic‑моделью, а вы не тратили время на ручную синхронизацию.

Если коротко, то самое важное, что дает SQLModel:

  • один класс для всего — это одновременно и SQLAlchemy-модель для запросов в базу, и pydantic.BaseModel для валидации данных от клиента;
  • нативный async/await — библиотека отлично дружит с асинхронными драйверами вроде asyncpg или psycopg без лишних костылей под капотом;
  • все в одном поле — через Field можно задавать и primary_key, и index, и default, и alias, не уходя в отдельный SQLColumn‑уровень, все настраивается в стиле Pydantic.

По сути, вы больше не пишете отдельный UserSchema, UserCreate, UserUpdate и параллельный UserTable — делаете одну модель User, а она уже и в базе, и в API.

  • Земцов Антон

    Земцов Антон

    Младший бэкенд-разработчик Python

Вот как может выглядеть объединенная модель:


      from typing import Optional
from sqlmodel import SQLModel, Field, Session, create_engine
from sqlalchemy import create_engine as sqlalchemy_create_engine
import httpx

class User(SQLModel, table=True):
    __tablename__ = "users"

    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(max_length=64)
    email: str = Field(max_length=128, index=True, unique=True)
    age: int = Field(ge=0)

# БД-движок и асинхронный клиент
# (для FastAPI — сюда же вписываются AsyncEngine / SessionLocal, но для примера оставлю sync)
sqlite_url = "sqlite:///example.db"
# Для продакшена лучше PostgreSQL + asyncpg / psycopg
engine = create_engine(sqlite_url)


def create_user(name: str, email: str, age: int) -> User:
    user = User(name=name, email=email, age=age)
    with Session(engine) as session:
        session.add(user)
        session.commit()
        session.refresh(user)
    return user


# В FastAPI это становится просто:
# @app.post("/users/", response_model=User)
# def create_user_api(user: User):  # тут уже User = SQLModel + pydantic, можно прямо в return
#     return create_user(user.name, user.email, user.age)

Теперь, когда вы валидируете входящий JSON через User(name=…, email=…), вы одновременно проверяете типы, max_length и ge. И при этом точно знаете, что эта же структура будет корректно записана в таблицу БД.

Стоит ли объединять все и всегда?

Несмотря на удобство, опытные разработчики часто отмечают, что SQLModel — это дополнительный слой поверх SQLAlchemy и Pydantic. В сложных проектах это может затруднить отладку и ограничить использование специфических фишек обеих библиотек.

Разные задачи — разные модели. Модель базы данных и DTO (передача данных) часто живут по разным правилам. Пытаясь засунуть все в один класс, можно получить перегруженную структуру, которую сложно поддерживать.

Поэтому SQLModel — отличный инструмент для быстрого старта и средних сервисов, но в огромных энтерпрайз-системах разделение моделей на «таблицы» и «схемы» часто остается более надежным и гибким решением.

Если в проде нужно отдать клиенту только часть полей (например, UserOut без пароля), в SQLModel это решается простым наследованием.


      class UserOut(User):
    # Reuse `User` fields, optionally redefining some
    pass

Вы создаете class UserOut(User) и переиспользуете все поля базовой модели. Схема в OpenAPI сгенерируется сама, а структура базы уже будет выверена заранее. В реальных проектах это избавляет от «расхождений», когда фронтенд ждет одно, а API отдает другое.

Следите за новостями и оставайтесь с Академией Selectel. В следующих постах разберем еще больше инструментов для ускорения разработки.