Skip to main content
FastAPI — асинхронный веб-фреймворк для Python, а SQLAlchemy — самая популярная ORM для Python. В этом руководстве вы определите типизированную модель SQLAlchemy, создадите для неё таблицу с помощью миграции Alembic и реализуете на FastAPI конечные точки для создания, чтения, обновления и удаления данных. Приложение использует драйвер asyncpg, а все соединения защищены TLS с полной проверкой сертификата.

Предварительные требования

  • uv и Python 3.11 или новее. Это руководство проверено на Python 3.13, FastAPI 0.142, SQLAlchemy 2.1, asyncpg 0.31, Alembic 1.20 и Uvicorn 0.54.
  • Аккаунт ClickHouse Cloud
  • psql для создания базы данных. Оператор CREATE DATABASE также можно выполнить в консоли SQL.

Создание сервиса ClickHouse Managed Postgres

В консоли ClickHouse Cloud нажмите New service и выберите Postgres. Инстанс будет готов через несколько минут. Пошаговое описание процесса см. в кратком руководстве.

Получение сведений о подключении

Нажмите Подключение на левой боковой панели вашего сервиса. В открывшемся окне отображаются имя пользователя, пароль, сервер и порт, а также переключатель Directly / via PgBouncer. В этом руководстве используются оба типа соединений:
  • PgBouncer (порт 6432) — для приложения. SQLAlchemy держит пул соединений в каждом воркере Uvicorn, а встроенный PgBouncer позволяет множеству воркеров и реплик совместно использовать небольшое число соединений с Postgres. PgBouncer работает в режиме пулинга транзакций (transaction pooling).
  • Прямое соединение (порт 5432) — для Alembic. Миграции выполняют DDL, могут работать долго и зависеть от состояния сеанса, поэтому они должны подключаться к Postgres напрямую.
Включите Use SSL и нажмите Download CA certificate. Сертификат также можно скачать в разделе Settings → CA Certificate. Он уникален для вашего инстанса, поэтому драйвер может с его помощью убедиться, что подключается именно к вашему серверу. При включённом Use SSL URL в окне заканчивается на sslmode=verify-full&sslrootcert=.... asyncpg не читает эти параметры, поэтому скопируйте из окна только пароль и хост. CA‑сертификат приложение передаёт непосредственно в коде (см. Настройка соединения).

Настройка проекта

Создайте проект и установите FastAPI, Uvicorn, SQLAlchemy с дополнительным модулем asyncio, asyncpg и Alembic:
Добавляете в существующее приложение?Пропустите команды uv init и mkdir. В своём проекте выполните uv add "sqlalchemy[asyncio]" asyncpg alembic, а затем переходите к разделу Настройка соединения. Если ваше приложение работает с SQLite через асинхронный SQLAlchemy:
  • Измените вызов create_async_engine так, чтобы он соответствовал вызову в app/db.py. В migrations/env.py импортируйте Base и свои модели из собственных модулей.
  • Уберите Base.metadata.create_all из кода, выполняемого при запуске. Alembic создаст ваши существующие таблицы в Postgres, но не перенесёт данные из SQLite.

Настройка соединения

Переместите скачанный CA‑сертификат в каталог проекта и переименуйте его в ca-certificate.pem. Создайте базу данных для приложения. Замените <PASSWORD> и хост значениями из модального окна Подключение:
Создайте файл .env в корне проекта или добавьте эти переменные в существующий. Оба URL различаются только портом, используют схему postgresql+asyncpg и не содержат параметров SSL:
.env
Путь в DATABASE_CA_CERT вычисляется относительно каталога, из которого вы запускаете команды. Если пароль содержит специальные символы, например @, / или #, примените к ним URL-кодирование. Не добавляйте .env в систему контроля версий. Создайте app/db.py. В этом файле настраиваются параметры TLS, создаются движок и фабрика сеансов, общая для всего приложения:
app/db.py
Объект ssl.SSLContext, созданный с PROTOCOL_TLS_CLIENT, требует действительный сертификат и проверяет имя хоста, что эквивалентно sslmode=verify-full. SQLAlchemy передаёт его в asyncpg через connect_args.
Передавайте CA‑сертификат в connect_args
  • Без аргумента ssl asyncpg использует TLS, если сервер его поддерживает, но сертификат не проверяет.
  • Не копируйте sslmode или sslrootcert из модального окна Подключение в URL. SQLAlchemy передаёт параметры URL в asyncpg как именованные аргументы, и установить соединение не удаётся из‑за ошибки TypeError: connect() got an unexpected keyword argument 'sslmode'.
  • В Python 3.13 и более поздних версиях контекст, созданный через ssl.create_default_context, приводит к ошибке certificate verify failed: Missing Authority Key Identifier, поскольку эта функция включает строгие проверки X.509. Создавайте контекст через ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), как показано выше.
  • Если указан неверный CA‑сертификат, соединение завершается ошибкой certificate verify failed: unable to get local issuer certificate. Если имя хоста не совпадает (например, при подключении по IP-адресу), возникает ошибка certificate verify failed: IP address mismatch.
Если вы предпочитаете хранить настройки TLS в URL или ваше приложение использует синхронные сеансы SQLAlchemy, используйте вместо asyncpg драйвер psycopg. Он основан на libpq и принимает параметры из модального окна Подключение, например postgresql+psycopg://...:5432/guide_fastapi?sslmode=verify-full&sslrootcert=ca-certificate.pem, причём как с create_engine, так и с create_async_engine.

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

Создайте файл app/models.py с типизированной моделью SQLAlchemy:
app/models.py

Выполнение миграций

Создайте окружение Alembic на основе асинхронного шаблона:
Замените содержимое сгенерированного файла migrations/env.py. Новая версия загружает ваши модели для autogenerate и подключается напрямую к Postgres с теми же настройками TLS, что и приложение:
migrations/env.py
Alembic игнорирует плейсхолдер sqlalchemy.url в alembic.ini, так как env.py создаёт движок самостоятельно. Сгенерируйте миграцию на основе модели и примените её:
Проверьте сгенерированный файл в migrations/versions и закоммитьте его вместе с кодом. Alembic записывает применённую ревизию в таблицу alembic_version. При каждом изменении модели заново выполняйте revision --autogenerate и upgrade head.

Добавьте конечные точки API

Создайте app/main.py. Каждый запрос получает собственный объект AsyncSession через зависимость FastAPI, а пул движка закрывается при завершении работы приложения:
app/main.py
SQLAlchemy выполняет все операторы одного сеанса в рамках одной транзакции, которая завершается вызовом session.commit. При закрытии сеанса все незакоммиченные изменения откатываются. При выполнении INSERT SQLAlchemy получает сгенерированные значения id и created_at через RETURNING, поэтому функции create_todo не требуется дополнительный запрос.

Запуск и проверка

Запустите приложение:
Во втором терминале создайте две задачи, отметьте первую как выполненную и удалите вторую:
Запрос DELETE возвращает 204 No Content. При запросе несуществующей задачи возвращается 404:
Откройте в браузере http://localhost:3112/todos, чтобы просмотреть список оставшихся задач, или http://localhost:3112/docs, чтобы опробовать конечные точки в интерактивной документации API FastAPI. Чтобы убедиться, что прямое соединение защищено TLS с проверкой сертификата, выполните запрос к pg_stat_ssl с теми же настройками TLS, что и в приложении:
Чтобы просмотреть данные в консоли, откройте консоль SQL на левой боковой панели вашего сервиса, разверните guide_fastapi, затем public и выберите таблицу todos. В соседней таблице alembic_version хранится история миграций. В таблице todos осталась одна строка: Чтобы остановить приложение, нажмите Ctrl+C.

Использование PgBouncer

Приложение работает через PgBouncer с настройками asyncpg и SQLAlchemy по умолчанию:
  • asyncpg выполняет каждый запрос как именованный подготовленный оператор на уровне протокола, а SQLAlchemy кэширует до 100 таких операторов для каждого соединения. Встроенный PgBouncer поддерживает эти операторы в режиме пулинга транзакций, а asyncpg освобождает их сообщением Close на уровне протокола, а не SQL-командой DEALLOCATE. Указывать statement_cache_size=0 или собственную функцию prepared_statement_name_func не нужно. Если вы всё же отключаете кэш операторов asyncpg, добавьте также в URL prepared_statement_cache_size=0, чтобы отключить кэш SQLAlchemy.
  • Настройки сеанса, заданные через SET, например search_path, не сохраняются в следующей транзакции и могут «утечь» к другим клиентам. Вместо этого используйте SET LOCAL внутри транзакции.
  • pg_stat_ssl показывает соединение между PgBouncer и Postgres, поэтому описанная выше проверка TLS через порт 6432 возвращает (False, None). При этом соединение приложения с PgBouncer всё равно проверяется: с неверным CA‑сертификатом подключение завершится такой же ошибкой.
  • Alembic не использует рекомендательные блокировки и выполняет каждый upgrade в одной транзакции, поэтому тоже работает через PgBouncer. Тем не менее для длительных миграций лучше использовать прямое соединение.
Изменение типа столбцаPgBouncer хранит подготовленные операторы в своих соединениях с Postgres. Если миграция изменяет тип столбца, возвращаемого запросом, например с integer на bigint, этот запрос через PgBouncer завершается ошибкой InvalidCachedStatementError: cached statement plan is invalid due to a database schema or configuration change — даже в новых соединениях — до тех пор, пока PgBouncer не заменит свои соединения с Postgres. Чтобы устранить проблему, закройте соединения PgBouncer с Postgres для этой базы данных через прямое соединение от имени того же пользователя, под которым подключается приложение. PgBouncer подключается к Postgres локально, поэтому у его соединений нет client_addr; прямые соединения запрос не затрагивает. PgBouncer откроет новые соединения, а каждое соединение приложения из пула может ещё один раз завершиться ошибкой, прежде чем SQLAlchemy обновит свой кэш:
Добавление столбца к этой проблеме не приводит, поскольку SQLAlchemy явно перечисляет выбираемые столбцы.

Дальнейшие шаги