Настройка SQLAlchemy для Python веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка SQLAlchemy для Python веб-приложения
Средняя
~1 рабочий день
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка SQLAlchemy для Python веб-приложения

SQLAlchemy — стандарт де-факто для работы с реляционными БД в Python. В версии 2.0 синтаксис запросов кардинально изменился: устаревший session.query(Model) заменён на select(Model). Настройка с нуля подразумевает версию 2.x.

pip install sqlalchemy[asyncio] asyncpg alembic
# для синхронного варианта:
pip install sqlalchemy psycopg2-binary alembic

Engine и сессия

Для FastAPI и других async-фреймворков используем асинхронный engine:

# app/database.py
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    async_sessionmaker,
    create_async_engine,
)
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/mydb"

engine = create_async_engine(
    DATABASE_URL,
    pool_size=10,
    max_overflow=20,
    pool_pre_ping=True,          # проверяет соединение перед использованием
    echo=False,                   # True — выводит SQL в stdout
)

AsyncSessionLocal = async_sessionmaker(
    engine,
    class_=AsyncSession,
    expire_on_commit=False,       # объекты доступны после commit без re-fetch
)


class Base(DeclarativeBase):
    pass

pool_pre_ping=True критично для production: без него соединения, разорванные прокси или фаерволом по таймауту, возвращают ошибку вместо прозрачного переподключения.

Dependency для FastAPI

# app/deps.py
from collections.abc import AsyncGenerator
from app.database import AsyncSessionLocal
from sqlalchemy.ext.asyncio import AsyncSession


async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

Модели

# app/models/user.py
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Enum, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
import enum


class UserRole(enum.Enum):
    admin = "admin"
    editor = "editor"
    viewer = "viewer"


class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False)
    password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    role: Mapped[UserRole] = mapped_column(
        Enum(UserRole), default=UserRole.viewer, nullable=False
    )
    created_at: Mapped[datetime] = mapped_column(
        server_default=func.now(), nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        server_default=func.now(), onupdate=func.now(), nullable=False
    )

    posts: Mapped[list["Post"]] = relationship(
        back_populates="author", lazy="selectin"
    )

Mapped с mapped_column — это новый типизированный API 2.0. Старый Column работает, но не даёт вывода типов в IDE.

lazy="selectin" для отношений в async-контексте — безопасная стратегия: SQLAlchemy выполнит отдельный SELECT ... WHERE id IN (...) вместо попытки ленивой загрузки, которая в async-режиме выбрасывает MissingGreenlet.

Запросы в стиле 2.0

from sqlalchemy import select, and_
from app.models.user import User
from app.models.post import Post


async def get_published_posts_with_authors(
    db: AsyncSession,
    limit: int = 20,
    offset: int = 0,
) -> list[Post]:
    stmt = (
        select(Post)
        .join(Post.author)
        .where(Post.status == "published")
        .order_by(Post.created_at.desc())
        .limit(limit)
        .offset(offset)
    )
    result = await db.execute(stmt)
    return list(result.scalars().all())


async def get_user_by_email(db: AsyncSession, email: str) -> User | None:
    stmt = select(User).where(User.email == email)
    result = await db.execute(stmt)
    return result.scalar_one_or_none()

Транзакции и savepoint

async def transfer_ownership(
    db: AsyncSession,
    post_id: int,
    new_author_id: int,
) -> None:
    # Вложенная транзакция через savepoint
    async with db.begin_nested():
        post = await db.get(Post, post_id)
        if post is None:
            raise ValueError(f"Post {post_id} not found")
        post.author_id = new_author_id
    # Внешняя транзакция коммитится отдельно

Alembic: настройка миграций

alembic init -t async alembic

Правим alembic/env.py для async:

from logging.config import fileConfig
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
from app.database import Base
# импортируем все модели, чтобы Base.metadata их видел
import app.models  # noqa: F401

config = context.config
fileConfig(config.config_file_name)
target_metadata = Base.metadata


def run_migrations_online():
    connectable = async_engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
    )

    async def do_run():
        async with connectable.connect() as connection:
            await connection.run_sync(
                context.configure,
                connection=connection,  # type: ignore[arg-type]
                target_metadata=target_metadata,
                compare_type=True,
            )
            async with context.begin_transaction():
                await connection.run_sync(context.run_migrations)

    import asyncio
    asyncio.run(do_run())


run_migrations_online()
alembic revision --autogenerate -m "create users"
alembic upgrade head

compare_type=True — Alembic будет замечать изменения типов колонок, не только добавление/удаление.

Сроки

Начальная настройка под FastAPI-проект: 1 день. Перевод существующего проекта с версии 1.4 (legacy Query API) на 2.0: 1–3 дня в зависимости от объёма моделей и нестандартных конструкций.