Установка библиотек и подключение базы данных 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.
В этом разделе мы рассмотрим три основных компонента:
- Прямое подключение через
connect(); - Пул подключений через
ConnectionPool; - Работа с результатами запросов через
cursor.
1. Подключение напрямую через connect()
Функция psycopg.connect() позволяет установить одно явное соединение с базой данных PostgreSQL.
Это базовый интерфейс, полезный для простых операций и ручного управления транзакциями.
Пример:
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().
Параметры подключения:
psycopg.connect(
host="localhost",
port=5432,
user="postgres",
password="secret",
dbname="mydb",
connect_timeout=10,
application_name="myapp"
)Также можно использовать DSN-строку:
psycopg.connect("postgresql://postgres:secret@localhost:5432/mydb")2. Пул подключений (рекомендуемый способ)
Объект ConnectionPool из модуля psycopg_pool управляет множеством соединений с базой данных. Это более производительный и устойчивый способ подключения в реальных приложениях.
Пример:
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)Преимущества:
- Повторное использование уже открытых соединений;
- Подходит для приложений с высокой нагрузкой;
Конфигурация пула:
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— метаинформация о колонках (имена, типы и т.д.).
Пример:
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:
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__ и подкаталоги в нем.
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 создайте виртуальное окружение и активируйте его:
python3 -m venv .venv
source .venv/bin/activateПоместите в файл requirements.txt содержимое:
fastapi
psycopg
psycopg-binary
psycopg_pool
python-dotenv
pydantic
python-jose[cryptography]
bcrypt
httpx
pytest
uvicorn[standard]
pydantic
regexВыполните команду
pip install -r requirements.txtУстановка переменных окружения
Использование переменных окружения в проекте позволяет отделить конфиденциальные и изменяемые настройки (например, параметры подключения к базе данных) от основного кода приложения. Это важно по нескольким причинам.
Во-первых, безопасность: данные вроде логина, пароля, адреса сервера и имени базы данных не должны попадать в систему контроля версий (например, Git), чтобы избежать утечек при публикации кода. Переменные окружения можно хранить в .env файле, который добавляется в .gitignore, или задавать напрямую в среде запуска (например, на сервере или в CI/CD).
Во-вторых, гибкость и удобство настройки: приложение можно разворачивать в разных средах — локально, на тестовом сервере, в продакшене — без изменения исходного кода. Достаточно задать переменные окружения для каждой среды.
В-третьих, читаемость и масштабируемость: конфигурационные значения собраны в одном месте, их проще менять и документировать. Это особенно важно в командной разработке и при работе с множеством сервисов и баз данных.
Для удобства разработки мы используем пакет python-dotenv, который умеет считывать переменные из файла .env. Пример заполнения этого файла представлен ниже.
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. Поместите в него следующий код:
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')}"
)Разберем, что происходит в этом коде.
Импорт библиотек
pythonimport os from psycopg_pool import ConnectionPoolos— стандартный модуль для работы с переменными окружения (os.getenv()).ConnectionPool— создаёт и управляет пулом соединений к PostgreSQL.
Создание пула подключений
pythonpool = ConnectionPool(conninfo=...)- Параметры подключения собираются в строку
conninfo, которая поддерживает стандартный синтаксис PostgreSQL:postgresql://user:password@host:port/dbname. - Пул будет автоматически переиспользовать соединения и управлять ими.
- Параметры подключения собираются в строку
Создание главного файла приложения, запуск приложения и проверка подключения к БД
Создайте файл app.py в каталоге src/. Поместите в него следующее содержимое:
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. Импорт библиотек
import os
from dotenv import load_dotenv
from fastapi import FastAPI, Response, status
from src.config.db import poolos— стандартный модуль Python для работы с переменными окружения (os.getenv()).load_dotenv()— загружает переменные из.envфайла вos.environ.- FastAPI — фреймворк для создания веб-сервера и REST API.
pool— пул подключений к PostgreSQL, созданный вsrc/config/db.py.
2. Загрузка переменных окружения
load_dotenv()- Загружает переменные из файла
.envв глобальное окружение. - После этого можно безопасно использовать
os.getenv("DB_USER")и другие переменные в коде.
3. Создание приложения и определение порта
app = FastAPI()
port = int(os.getenv("PORT", 3000))app— это экземпляр FastAPI-приложения. Через него определяются маршруты,middlewaresи обработчики ошибок.port— порт, на котором будет запущено приложение (берётся из.envили устанавливается значение по умолчанию —3000).
4. Маршрут /api/health-check
@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)
| Код | Назначение |
|---|---|
| 200 | OK — успешный запрос |
| 201 | Created — успешно создан ресурс (чаще при POST) |
| 204 | No Content — запрос успешен, но тело ответа отсутствует (например, DELETE) |
🟡 3xx — Перенаправление (Redirection)
| Код | Назначение |
|---|---|
| 301 | Moved Permanently — постоянное перенаправление |
| 302 | Found — временное перенаправление |
| 304 | Not Modified — использовать закешированную версию ресурса |
🔴 4xx — Ошибки клиента (Client Errors)
| Код | Назначение |
|---|---|
| 400 | Bad Request — некорректный запрос |
| 401 | Unauthorized — требуется авторизация |
| 403 | Forbidden — доступ запрещён, даже при наличии авторизации |
| 404 | Not Found — запрашиваемый ресурс не найден |
| 409 | Conflict — конфликт запроса (например, при создании дубликата) |
| 422 | Unprocessable Entity — ошибка обработки данных (например, ошибка валидации формы) |
🔴 5xx — Ошибки сервера (Server Errors)
| Код | Назначение |
|---|---|
| 500 | Internal Server Error — внутренняя ошибка сервера |
| 502 | Bad Gateway — неверный ответ от стороннего сервера |
| 503 | Service Unavailable — сервер временно недоступен (например, перегружен) |
| 504 | Gateway 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. Запуск сервера
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автоматически перезапускает сервер при изменении кода (удобно при разработке).
Чтобы запустить наше приложение, выполните команду
python3 src/app.pyЕсли все сделано правильно, то в консоли будет вывод:
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. Сделать это можно несколькими способами.
С помощью утилиты
curlВ другой консоли (так как в первой у нас запущено приложение) выполните команду:
bashcurl http://localhost:3000/api/health-checkВ случае успеха вы увидите ответ
OK. Иначе в консоли с приложением будет ошибка.С помощью браузера.
Откройте любой браузер и перейдите по адресу
http://localhost:3000/api/health-check. При таком действии браузер отправляетGETзапрос. Если все нормально, то вы также увидите текстOk.С помощью программного обеспечения Postman, об этом далее.
Итог
Мы создали минимальный, но уже работоспособный каркас веб-приложения на основе FastAPI.
Вы узнали, как:
- подключать необходимые зависимости (
fastapi,psycopg,python-dotenvи др.); - использовать
.envфайл для хранения конфигурации; - подключаться к PostgreSQL через пул соединений (
psycopg_pool); - реализовать базовую маршрутизацию (
/api/health-check) для проверки доступности БД;
Этот фундаментальный каркас послужит основой для дальнейшей разработки полноценного REST API.