Skip to content

Установка библиотек и подключение базы данных PostgreSQL к приложению

Введение

В рамках этого учебного курса мы будем разрабатывать веб-приложение с использованием Python, FastAPI и базы данных PostgreSQL.
Для взаимодействия между приложением и базой данных мы будем использовать библиотеку psycopg, официальную реализацию PostgreSQL-драйвера для Python.

psycopg — это высокопроизводительный адаптер PostgreSQL для Python. В версии 3 он стал более гибким, поддерживает как синхронный, так и асинхронный режим работы, удобную работу с типами PostgreSQL, пул подключений и многое другое.

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

Преимущества psycopg:

  • высокая производительность;
  • простота подключения;
  • поддержка параметризованных запросов (безопасность от SQL-инъекций);
  • поддержка пулов соединений;
  • хорошая совместимость с PostgreSQL-типами (JSON, UUID, ARRAY, TIMESTAMP и т.д.);
  • активная поддержка и документация.

Благодаря этому мы сможем использовать всю мощь PostgreSQL прямо из кода на Python — просто, эффективно и без необходимости использовать ORM.

Основы API библиотеки psycopg

Библиотека psycopg предоставляет несколько ключевых интерфейсов для работы с PostgreSQL в соответствии со стандартом Python DB API 2.0.
В этом разделе мы рассмотрим три основных компонента:

  1. Прямое подключение через connect();
  2. Пул подключений через ConnectionPool;
  3. Работа с результатами запросов через cursor.

1. Подключение напрямую через connect()

Функция psycopg.connect() позволяет установить одно явное соединение с базой данных PostgreSQL.
Это базовый интерфейс, полезный для простых операций и ручного управления транзакциями.

Пример:

python
import psycopg

conn = psycopg.connect(
    host="localhost",
    port=5432,
    user="postgres",
    password="secret",
    dbname="mydb"
)

with conn.cursor() as cur:
    cur.execute("SELECT NOW()")
    result = cur.fetchone()
    print(result)

conn.close()

Особенности:

  • Соединение создаётся вручную и закрывается вручную (conn.close()).
  • По умолчанию psycopg использует неавтоматические транзакции: нужно вызывать conn.commit() или conn.rollback().

Параметры подключения:

python
psycopg.connect(
    host="localhost",
    port=5432,
    user="postgres",
    password="secret",
    dbname="mydb",
    connect_timeout=10,
    application_name="myapp"
)

Также можно использовать DSN-строку:

python
psycopg.connect("postgresql://postgres:secret@localhost:5432/mydb")

2. Пул подключений (рекомендуемый способ)

Объект ConnectionPool из модуля psycopg_pool управляет множеством соединений с базой данных. Это более производительный и устойчивый способ подключения в реальных приложениях.

Пример:

python
from psycopg_pool import ConnectionPool

pool = ConnectionPool(conninfo="postgresql://postgres:secret@localhost:5432/mydb")

with pool.connection() as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT * FROM users WHERE id = %s", (1,))
        row = cur.fetchone()
        print(row)

Преимущества:

  • Повторное использование уже открытых соединений;
  • Подходит для приложений с высокой нагрузкой;

Конфигурация пула:

python
pool = ConnectionPool(
    conninfo="...",
    min_size=1,            # минимальное количество соединений
    max_size=10,           # максимальное количество соединений
    max_idle=30,           # максимальное время простоя соединения (секунды)
    timeout=5              # таймаут ожидания соединения из пула (секунды)
)

3. Результаты выполнения SQL-запросов

После выполнения cursor.execute(...) результат запроса доступен через курсор. Типичный объект результата включает:

  • cursor.fetchone() — получить одну строку;
  • cursor.fetchall() — получить все строки;
  • cursor.rowcount — количество строк, возвращённых или затронутых;
  • cursor.description — метаинформация о колонках (имена, типы и т.д.).

Пример:

python
with conn.cursor() as cur:
    cur.execute("SELECT 1 AS one, 2 AS two;")
    print(cur.description[0].name)  # 'one'
    print(cur.description[1].name)  # 'two'
    print(cur.fetchall())           # [(1, 2)]

Если нужно получать строки в виде словарей (dict), можно задать row_factory:

python
with conn.cursor(row_factory=psycopg.rows.dict_row) as cur:
    cur.execute("SELECT id, name FROM users;")
    for row in cur.fetchall():
        print(row["id"], row["name"])

Структура проекта

Создайте каталог gophertalk-backend-fastapi. В нем создайте подкаталог src, файлы .env, requirements.txt, README.md. В каталоге src создайте каталоги, указанные ниже, а также пустой файл app.py. Также создайте каталог __tests__ и подкаталоги в нем.

bash
gophertalk-backend-fastapi/
├── src/
   ├── controllers/       # Обработка HTTP-запросов
   ├── services/          # Бизнес-логика
   ├── repositories/      # Работа с БД (SQL-запросы)
   ├── routes/            # Определение маршрутов
   ├── dependencies/      # Общие зависимости FastAPI
   ├── packages/          # скачанные пакеты с зависимостями
   ├── config/            # Конфигурация проекта
   ├── utils/             # Вспомогательные функции
   ├── dto/               # Data transfer objects и pydantic модели
   └── app.py             # Инициализация приложения
├── __tests__              # unit тесты
   ├── controllers/
   ├── services/
   └── repositories/
├── .env                   # Переменные окружения
├── requirements.txt
└── README.md

Инициализация проекта и установка зависимостей

В корне проекта в папке gophertalk-backend-fastapi создайте виртуальное окружение и активируйте его:

bash
python3 -m venv .venv
source .venv/bin/activate

Поместите в файл requirements.txt содержимое:

text
fastapi
psycopg
psycopg-binary
psycopg_pool
python-dotenv
pydantic
python-jose[cryptography]
bcrypt
httpx
pytest
uvicorn[standard]
pydantic
regex

Выполните команду

bash
pip install -r requirements.txt

Установка переменных окружения

Использование переменных окружения в проекте позволяет отделить конфиденциальные и изменяемые настройки (например, параметры подключения к базе данных) от основного кода приложения. Это важно по нескольким причинам.

Во-первых, безопасность: данные вроде логина, пароля, адреса сервера и имени базы данных не должны попадать в систему контроля версий (например, Git), чтобы избежать утечек при публикации кода. Переменные окружения можно хранить в .env файле, который добавляется в .gitignore, или задавать напрямую в среде запуска (например, на сервере или в CI/CD).

Во-вторых, гибкость и удобство настройки: приложение можно разворачивать в разных средах — локально, на тестовом сервере, в продакшене — без изменения исходного кода. Достаточно задать переменные окружения для каждой среды.

В-третьих, читаемость и масштабируемость: конфигурационные значения собраны в одном месте, их проще менять и документировать. Это особенно важно в командной разработке и при работе с множеством сервисов и баз данных.

Для удобства разработки мы используем пакет python-dotenv, который умеет считывать переменные из файла .env. Пример заполнения этого файла представлен ниже.

bash
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=postgres
ACCESS_TOKEN_EXPIRES=3600
REFRESH_TOKEN_EXPIRES=86400
ACCESS_TOKEN_SECRET=super_secret_access_token_key
REFRESH_TOKEN_SECRET=super_secret_refresh_token_key

Значения переменных установите сами. Вам нужен сервер PostgreSQL, база данных в нем и учетная запись с правами в этой БД.

Значения переменных ACCESS_TOKEN_EXPIRES, REFRESH_TOKEN_EXPIRES, ACCESS_TOKEN_SECRET и REFRESH_TOKEN_SECRET не изменяйте, они понадобятся в дальнейшем.

Настройка подключения к PostgreSQL

Создайте файл db.py в каталоге src/config. Поместите в него следующий код:

python
import os
from psycopg_pool import ConnectionPool

pool = ConnectionPool(
    conninfo=f"postgresql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
             f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
)

Разберем, что происходит в этом коде.

  1. Импорт библиотек

    python
    import os
    from psycopg_pool import ConnectionPool
    • os — стандартный модуль для работы с переменными окружения (os.getenv()).
    • ConnectionPool — создаёт и управляет пулом соединений к PostgreSQL.
  2. Создание пула подключений

    python
    pool = ConnectionPool(conninfo=...)
    • Параметры подключения собираются в строку conninfo, которая поддерживает стандартный синтаксис PostgreSQL: postgresql://user:password@host:port/dbname.
    • Пул будет автоматически переиспользовать соединения и управлять ими.

Создание главного файла приложения, запуск приложения и проверка подключения к БД

Создайте файл app.py в каталоге src/. Поместите в него следующее содержимое:

python
import os

from dotenv import load_dotenv
from fastapi import FastAPI, Response, status

load_dotenv()

from config.db import pool

app = FastAPI()
port = int(os.getenv("PORT", 3000))


@app.get("/api/health-check")
def health_check():
    try:
        with pool.connection() as conn:
            with conn.cursor() as cur:
                cur.execute("SELECT 1")
                cur.fetchone()
        return Response(content="OK", status_code=status.HTTP_200_OK)
    except Exception:
        return Response(content="DB connection failed", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)


if __name__ == "__main__":
    import uvicorn
    uvicorn.run("app:app", host="0.0.0.0", port=port, reload=True)

Этот код — минимальный сервер на FastAPI, подключённый к базе данных PostgreSQL через psycopg. Разберём его подробнее.


1. Импорт библиотек

python
import os
from dotenv import load_dotenv
from fastapi import FastAPI, Response, status
from src.config.db import pool
  • os — стандартный модуль Python для работы с переменными окружения (os.getenv()).
  • load_dotenv() — загружает переменные из .env файла в os.environ.
  • FastAPI — фреймворк для создания веб-сервера и REST API.
  • pool — пул подключений к PostgreSQL, созданный в src/config/db.py.

2. Загрузка переменных окружения

python
load_dotenv()
  • Загружает переменные из файла .env в глобальное окружение.
  • После этого можно безопасно использовать os.getenv("DB_USER") и другие переменные в коде.

3. Создание приложения и определение порта

python
app = FastAPI()
port = int(os.getenv("PORT", 3000))
  • app — это экземпляр FastAPI-приложения. Через него определяются маршруты, middlewares и обработчики ошибок.
  • port — порт, на котором будет запущено приложение (берётся из .env или устанавливается значение по умолчанию — 3000).

4. Маршрут /api/health-check

python
@app.get("/api/health-check")
def health_check():
    try:
        with pool.connection() as conn:
            with conn.cursor() as cur:
                cur.execute("SELECT 1")
                cur.fetchone()
        return Response(content="OK", status_code=status.HTTP_200_OK)
    except Exception:
        return Response(content="DB connection failed", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
  • Это технический GET-маршрут для проверки работоспособности приложения и соединения с PostgreSQL.
  • Выполняется простой запрос SELECT 1 внутри пула подключений.
  • Если соединение успешно — возвращается 200 OK, иначе 500 DB connection failed.
HTTP коды

HTTP-статус-коды делятся на 5 категорий, каждая из которых имеет своё назначение. Вот некоторые из них:

🔵 1xx — Информационные (Informational)

КодНазначение
100Продолжайте (Continue) — сервер получил заголовки и ждёт тело запроса
101Переключение протоколов (Switching Protocols) — например, WebSocket

🟢 2xx — Успешные (Success)

КодНазначение
200OK — успешный запрос
201Created — успешно создан ресурс (чаще при POST)
204No Content — запрос успешен, но тело ответа отсутствует (например, DELETE)

🟡 3xx — Перенаправление (Redirection)

КодНазначение
301Moved Permanently — постоянное перенаправление
302Found — временное перенаправление
304Not Modified — использовать закешированную версию ресурса

🔴 4xx — Ошибки клиента (Client Errors)

КодНазначение
400Bad Request — некорректный запрос
401Unauthorized — требуется авторизация
403Forbidden — доступ запрещён, даже при наличии авторизации
404Not Found — запрашиваемый ресурс не найден
409Conflict — конфликт запроса (например, при создании дубликата)
422Unprocessable Entity — ошибка обработки данных (например, ошибка валидации формы)

🔴 5xx — Ошибки сервера (Server Errors)

КодНазначение
500Internal Server Error — внутренняя ошибка сервера
502Bad Gateway — неверный ответ от стороннего сервера
503Service Unavailable — сервер временно недоступен (например, перегружен)
504Gateway Timeout — истекло время ожидания ответа от другого сервиса
HTTP методы

HTTP-методы определяют тип действия, которое клиент (например, браузер или frontend-приложение) хочет выполнить на сервере по заданному URL. Они являются основой для построения REST API и позволяют реализовывать операции чтения, создания, обновления и удаления ресурсов.

Каждый метод имеет своё назначение и семантику, и его правильное использование помогает сделать API логичным, безопасным и удобным для использования.

МетодНазначениеИдёмпотентностьБезопасностьИспользуется в REST для
GETПолучение данных с сервера✅ Да✅ ДаЧтение
POSTОтправка новых данных на сервер (создание ресурса)❌ Нет❌ НетСоздание
PUTПолное обновление ресурса (замена)✅ Да❌ НетОбновление
PATCHЧастичное обновление ресурса❌ Нет❌ НетЧастичное обновление
DELETEУдаление ресурса✅ Да❌ НетУдаление
HEADКак GET, но без тела ответа (используется для проверки заголовков, кеша и т.д.)✅ Да✅ ДаПроверка доступности
OPTIONSВозвращает допустимые методы для указанного ресурса (применяется для CORS)✅ Да✅ ДаОбнаружение возможностей

Если метод идемпотентен, это значит, что повторный вызов этого метода не изменит результат. Например:

  • GET /users вернёт один и тот же список при каждом вызове;

  • DELETE /user/5 удалит пользователя, и повторный вызов уже ничего не изменит (если пользователь был удалён в первый раз);

  • POST /users не идемпотентен — при каждом вызове может создаваться новый пользователь.

Безопасный HTTP-метод — это метод, который не изменяет состояние сервера. Он используется только для получения информации, и его выполнение не должно иметь побочных эффектов (например, создавать, изменять или удалять данные). Например:

  • GET — безопасен, потому что просто читает данные;

  • POST — не безопасен, потому что может создавать ресурсы или выполнять действия.

5. Запуск сервера

python
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("app:app", host="0.0.0.0", port=PORT, reload=True)
  • Это точка входа для запуска приложения.
  • Используется сервер uvicorn, совместимый с ASGI, который запускает FastAPI-приложение.
  • Аргумент "app:app" означает: импортировать объект app из файла app.py.
  • Опция reload=True автоматически перезапускает сервер при изменении кода (удобно при разработке).

Чтобы запустить наше приложение, выполните команду

bash
python3 src/app.py

Если все сделано правильно, то в консоли будет вывод:

bash
INFO:     Uvicorn running on http://0.0.0.0:3000 (Press CTRL+C to quit)
INFO:     Started reloader process [90112] using WatchFiles
INFO:     Started server process [90118]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Чтобы проверить, что подключение к БД выполнено успешно, необходимо сделать GET запрос по адресу http://localhost:3000/api/health-check. Сделать это можно несколькими способами.

  1. С помощью утилиты curl

    В другой консоли (так как в первой у нас запущено приложение) выполните команду:

    bash
    curl http://localhost:3000/api/health-check

    В случае успеха вы увидите ответ OK. Иначе в консоли с приложением будет ошибка.

  2. С помощью браузера.

    Откройте любой браузер и перейдите по адресу http://localhost:3000/api/health-check. При таком действии браузер отправляет GET запрос. Если все нормально, то вы также увидите текст Ok.

  3. С помощью программного обеспечения Postman, об этом далее.

Итог

Мы создали минимальный, но уже работоспособный каркас веб-приложения на основе FastAPI.
Вы узнали, как:

  • подключать необходимые зависимости (fastapi, psycopg, python-dotenv и др.);
  • использовать .env файл для хранения конфигурации;
  • подключаться к PostgreSQL через пул соединений (psycopg_pool);
  • реализовать базовую маршрутизацию (/api/health-check) для проверки доступности БД;

Этот фундаментальный каркас послужит основой для дальнейшей разработки полноценного REST API.