Во многих уроках по FastAPI нас учат писать всё в одном файле main.py, иногда создавая дополнительные файлы, но это случается редко. Такой подход помогает быстро разобраться с основами: созданием простых маршрутов или подключением SQLAlchemy. Однако он негативно сказывается на понимании архитектуры приложения, а также на его читабельности и поддерживаемости.
В этом посте мы начнём разрабатывать архитектуру нашего будущего сервиса и будем придерживаться более структурированного подхода.
Прежде чем углубляться в детали архитектуры, давайте упростим запуск приложения и создадим файл для переменных окружения, чтобы облегчить настройку и поддержку проекта в дальнейшем.
Примечание: В проекте с самого начала будет использоваться СУБД PostgreSQL. Если у вас нет удалённого сервера с БД, не переживайте. Я написал инструкцию, как запустить PostgreSQL на локальной машине с помощью Docker, в статье: "Docker 3. Контейнер с PostgreSQL".
Важно: эта статья — часть серии «Веб-сервис на FastAPI». Она опирается на код и архитектурные решения, описанные в предыдущих материалах. Если вы попали сюда напрямую, некоторые места могут показаться неполными или слишком короткими — это нормально, просто в рамках цикла мы практически не повторяем уже разобранное.
Запуск с использованием 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.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
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
 
           
                    
Комментарии
Оставить комментарийВойдите, чтобы оставить комментарий.
uvicorn.run(app="lkeep_fastapi.main:app", reload=True) - хорошая вещь для запуска.
postgresql+asyncpg - это бы тоже вынести в .env, но не обязательно.
длина урла для базы данных привышает 120 символов.
У классов нет текстового описания в коде пайтон
Сделай вставку сообщения автоматическое в поле редактирования, а то при редактировании пустое поле.
DB_ECHO - вот про это я не знал, полезная весч