FastAPI 2. Подготовка проекта
В этом посте мы рассмотрим принципы организации структуры проекта на FastAPI, упростим запуск приложения с помощью Poetry и создадим файл для переменных окружения, необходимых для подключения к PostgreSQL.
Реклама
Во многих уроках по FastAPI нас учат писать всё в одном файле main.py
, иногда создавая дополнительные файлы, но это случается редко. Такой подход помогает быстро разобраться с основами: созданием простых маршрутов или подключением SQLAlchemy. Однако он негативно сказывается на понимании архитектуры приложения, а также на его читабельности и поддерживаемости.
В этом посте мы начнём разрабатывать архитектуру нашего будущего сервиса и будем придерживаться более структурированного подхода.
Прежде чем углубляться в детали архитектуры, давайте упростим запуск приложения и создадим файл для переменных окружения, чтобы облегчить настройку и поддержку проекта в дальнейшем.
Примечание: В проекте с самого начала будет использоваться СУБД PostgreSQL. Если у вас нет удалённого сервера с БД, не переживайте. Я написал инструкцию, как запустить PostgreSQL на локальной машине с помощью Docker, в статье: "Docker 3. Контейнер с PostgreSQL".
Запуск с использованием Poetry
Точка входа для Poetry
В предыдущей статье мы запускали проект, вручную переходя в нужную директорию и выполняя команду для запуска uvicorn
. Давайте оптимизируем этот процесс и сделаем его удобнее.
Откроем файл main.py
и под нашим тестовым маршрутом добавим функцию start
. Она не будет принимать аргументов и станет нашей "точкой входа" в проект через Poetry.
В теле функции вызовем uvicorn
с методом .run()
. В качестве аргументов передадим два именованных параметра:
app
— строку, содержащую имя файла, включая имя пакета и экземпляр классаFastAPI()
.reload
— установим значениеTrue
. Это позволит серверу автоматически перезапускаться при изменении файлов, аналогично тому, как это работает в Django.
Не забудьте импортировать uvicorn
в начале файла!
Пример кода функции:
import uvicorn
def start():
uvicorn.run(app="lkeep.main:app", reload=True)
Команда запуска
Теперь откроем файл pyproject.toml
. После блока [build-system]
добавим новый блок [project.scripts]
.
В этом блоке создадим переменную app
, где укажем строку, аналогичную той, что использовалась ранее в uvicorn
, но вместо app
укажем функцию start
.
Пример кода нового блока:
[project.scripts]
app = "lkeep.main:start"
Что мы сделали? Мы указали, что при вызове команды poetry run app
в терминале будет вызвана функция start()
из файла main.py
.
После добавления пользовательского скрипта в pyproject.toml
, необходимо выполнить повторную инициализацию. Для этого выполните команду:
poetry install
Проверка и чистка
Проверим, что всё работает корректно:
- Откройте терминал и убедитесь, что вы находитесь в корневой директории проекта.
- Выполните команду
poetry run app
.
Если всё настроено правильно, запустится сервер uvicorn
, и появится ссылка для доступа к сайту. Перейдите по ней. Если вы увидите ответ от маршрута, значит, всё работает.
Остановите сервер сочетанием клавиш CTRL+C
.
Теперь откройте файл main.py
и удалите тестовый маршрут, так как в главном файле не рекомендуется хранить маршруты.
Пример финального содержимого main.py
:
import uvicorn
from fastapi import FastAPI
app = FastAPI()
def start():
uvicorn.run(app="lkeep_fastapi.main:app", reload=True)
Переменные среды для "секретов"
Нередко в коде необходимо прописывать "секретные" данные, такие как логины, пароли и токены. Эти данные принято "прятать" в переменные окружения. Если вы работаете над проектом в команде или планируете опубликовать его на GitHub, сокрытие важных данных становится обязательным.
В этом посте мы будем использовать файл .env
для хранения переменных окружения.
- В корневой директории проекта создайте файл
.env
. - Если у вас подключена система контроля версий (git), обязательно добавьте этот файл в
.gitignore
, чтобы избежать его случайной загрузки в репозиторий.
Пример содержимого .env
:
DB_NAME=db_name
DB_USER=db_user
DB_PASSWORD=db_password
DB_HOST=localhost
DB_PORT=5432
DB_ECHO=True
Мы прописали следующие параметры:
DB_NAME
— имя базы данных.DB_USER
— пользователь базы данных.DB_PASSWORD
— пароль пользователя.DB_HOST
— адрес хоста базы данных (localhost, если база данных на вашей машине, или адрес удалённого сервера).DB_PORT
— порт базы данных, по умолчанию 5432.DB_ECHO
— включение отладки для SQLAlchemy. При значенииTrue
в консоль будут выводиться все SQL-запросы. Это полезно при разработке, но сильно загружает систему. Удостоверьтесь, что эта функция отключена, когда в ней нет необходимости.
С подготовкой завершено. Перейдём к созданию архитектуры.
Настройки подключения к БД
Для работы с базой данных будем использовать ORM SQLAlchemy
, асинхронный клиент asyncpg
и библиотеку pydantic-settings
для управления настройками.
Описание библиотек
SQLAlchemy
— это библиотека Python, предназначенная для работы с базами данных. Она позволяет представлять базы данных в виде объектов Python, что упрощает выполнение операций, таких как вставка, обновление и удаление данных. SQLAlchemy также предоставляет возможность формулировать запросы к базе данных на языке Python, избавляя от необходимости писать SQL-код вручную. Это делает код более читаемым и облегчает его поддержку.asyncpg
— асинхронная библиотека для работы с PostgreSQL в Python. Она предоставляет API, которое позволяет выполнять операции с базой данных, такие как выполнение запросов и обработка результатов, в асинхронном режиме. Это помогает улучшить производительность приложения, так как выполнение других задач не блокируется во время ожидания ответа от базы данных.Pydantic-Settings
— библиотека для управления настройками в Python-проектах. Она позволяет задавать структуру настроек, проверять их корректность, загружать их из различных источников (например, из файлов или переменных окружения) и сохранять в файл или базу данных.
Чтобы добавить эти библиотеки в проект, выполните команду:
poetry add sqlalchemy asyncpg pydantic-settings
Создание настроек
В пакете lkeep
, где находится файл main.py
, создадим новый пакет core
. Внутри создадим файл settings.py
.
Класс DBSettings
Создадим класс DBSettings
, который будет унаследован от BaseSettings
из библиотеки pydantic-settings
.
Внутри класса пропишем шесть полей, соответствующих параметрам для подключения к базе данных:
db_name: str
— имя базы данных.db_user: str
— имя пользователя базы данных.db_password: SecretStr
— пароль пользователя, используя типSecretStr
, который скрывает значение в логах.db_host: str
— хост, где расположена база данных (например,localhost
или IP-адрес).db_port: int
— порт базы данных (по умолчанию 5432 для PostgreSQL).db_echo: bool
— флаг для включения или отключения логирования SQL-запросов (полезно для отладки).
Библиотека pydantic-settings
является надстройкой над pydantic
, которая используется в FastAPI для работы с данными настроек. Она требует обязательного использования аннотаций типов для всех полей.
Обратите внимание на использование типа данных SecretStr
для поля db_password
. Это позволяет скрыть значение пароля в логах и представлении объекта, что является важной мерой безопасности.
После определения полей, добавим настройку для работы с .env
-файлом. Это делается с помощью поля model_config
, в котором мы создаём экземпляр класса SettingsConfigDict
. Мы задаём следующие параметры:
env_file
— путь до файла с переменными окружения.env_file_encoding
— кодировка файла (например,utf8
).extra
— указываем, что с переменными, не указанными в классе, нужно делать (в данном случае — игнорировать их).
Теперь создадим метод db_url
, который будет возвращать строку подключения к базе данных. Мы поместим его в декоратор @property
, чтобы обращаться к результату как к обычному полю. В этом методе соберём строку подключения, используя данные, прописанные в полях класса.
f"postgresql+asyncpg://{self.db_user}:{self.db_password.get_secret_value()}@{self.db_host}:{self.db_port}/{self.db_name}"
Не забудьте, что для получения значения пароля нужно использовать метод .get_secret_value()
, так как пароль хранится в защищённом формате.
Что означает строка postgresql+asyncpg
? Это описание базы данных (postgresql
) и драйвера подключения (asyncpg
). Всё до знака +
— это тип базы данных, а всё после — это библиотека для работы с этим типом базы данных. Например, для MySQL может быть mysql+aiomysql
, для SQLite — sqlite+pysqlite
.
Пример кода:
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class DBSettings(BaseSettings):
db_name: str
db_user: str
db_password: SecretStr
db_host: str
db_port: int
db_echo: bool
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
@property
def db_url(self):
return f"postgresql+asyncpg://{self.db_user}:{self.db_password.get_secret_value()}@{self.db_host}:{self.db_port}/{self.db_name}"
Класс Settings
Теперь создадим класс Settings
, который также будет унаследован от BaseSettings
из библиотеки pydantic-settings
. Внутри класса пропишем поле db_settings
, которое будет являться экземпляром уже созданного класса DBSettings
.
Поле db_settings
будет отвечать за хранение настроек подключения к базе данных. Так как мы используем pydantic-settings
, инициализация настроек будет происходить автоматически из .env
файла, если он присутствует.
Также, в самом низу файла создадим переменную settings
, которая будет экземпляром класса Settings
. Это позволит нам обращаться к настройкам приложения в любом месте кода.
Пример реализации:
from pydantic import BaseSettings
from .settings import DBSettings
class Settings(BaseSettings):
db_settings: DBSettings = DBSettings() # Экземпляр класса DBSettings
# Инициализация переменной settings
settings = Settings()
При инициализации приложения в переменной settings
будет создан объект класса Settings
, и вы сможете обращаться к его полям и методам. Например, для получения строки подключения к базе данных можно использовать:
db_url = settings.db_settings.db_url
Что важно:
- Инициализация
DBSettings
: Мы инициализируем полеdb_settings
значениемDBSettings()
, что создаёт объект настроек для подключения к базе данных. Это позволяет легко обращаться к этим настройкам через экземплярsettings
. - Гибкость: Теперь все настройки для базы данных находятся в одном месте, и вы можете легко добавлять новые настройки или изменять существующие, просто обновив класс
Settings
. - Использование настроек: Используя экземпляр
settings
, вы можете удобно получать доступ ко всем параметрам приложения, включая параметры подключения к базе данных, без необходимости напрямую взаимодействовать с.env
файлом.
Заключение
Продумывание архитектуры приложения – крайне важная задача, которой часто пренебрегают, что может привести к трудностям в будущем. В нашем случае мы подошли к проектированию с осознанием всех ключевых аспектов, таких как безопасность данных, удобство работы с настройками и поддержка асинхронности для более эффективной работы с базой данных. Мы учли лучшие практики и применили надёжные библиотеки для упрощения разработки и её масштабируемости.
Использование .env
файла для хранения конфиденциальных данных и pydantic-settings
для управления настройками позволяет нам централизованно и безопасно работать с конфигурацией проекта. Мы также внедрили поддержку асинхронной работы с базой данных с использованием SQLAlchemy
и asyncpg
, что улучшает производительность и отклик приложения.
Репозиторий проекта в GitHub: https://github.com/proDreams/lkeep
Репозиторий проекта в "GIT на салфетке": https://git.pressanybutton.ru/proDream/lkeep
Все статьи