Cat

FastAPI 2. Подготовка проекта

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

Сервис на FastAPI proDream 05 Сентябрь 2024 Просмотров: 766

Во многих уроках по FastAPI нас учат писать всё в одном файле main.py, иногда создавая дополнительные файлы, но это случается редко. Такой подход помогает быстро разобраться с основами: созданием простых маршрутов или подключением SQLAlchemy. Однако он негативно сказывается на понимании архитектуры приложения, а также на его читабельности и поддерживаемости.

В этом посте мы начнём разрабатывать архитектуру нашего будущего сервиса и будем придерживаться более структурированного подхода.

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

Примечание: В проекте с самого начала будет использоваться СУБД PostgreSQL. Если у вас нет удалённого сервера с БД, не переживайте. Я написал инструкцию, как запустить PostgreSQL на локальной машине с помощью Docker, в статье: "Docker 3. Контейнер с PostgreSQL".


 

Запуск с использованием Poetry

 

Точка входа для Poetry

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

Откроем файл main.py и под нашим тестовым маршрутом добавим функцию start. Она не будет принимать аргументов и станет нашей "точкой входа" в проект через Poetry.

В теле функции вызовем uvicorn с методом .run(). В качестве аргументов передадим два именованных параметра:

  1. app — строку, содержащую имя файла, включая имя пакета и экземпляр класса FastAPI().
  2. 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

 

Проверка и чистка

Проверим, что всё работает корректно:

  1. Откройте терминал и убедитесь, что вы находитесь в корневой директории проекта.
  2. Выполните команду 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 для хранения переменных окружения.

  1. В корневой директории проекта создайте файл .env.
  2. Если у вас подключена система контроля версий (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 для управления настройками.

 

Описание библиотек

  1. SQLAlchemy — это библиотека Python, предназначенная для работы с базами данных. Она позволяет представлять базы данных в виде объектов Python, что упрощает выполнение операций, таких как вставка, обновление и удаление данных. SQLAlchemy также предоставляет возможность формулировать запросы к базе данных на языке Python, избавляя от необходимости писать SQL-код вручную. Это делает код более читаемым и облегчает его поддержку.
  2. asyncpg — асинхронная библиотека для работы с PostgreSQL в Python. Она предоставляет API, которое позволяет выполнять операции с базой данных, такие как выполнение запросов и обработка результатов, в асинхронном режиме. Это помогает улучшить производительность приложения, так как выполнение других задач не блокируется во время ожидания ответа от базы данных.
  3. 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

 

Что важно:

  1. Инициализация DBSettings: Мы инициализируем поле db_settings значением DBSettings(), что создаёт объект настроек для подключения к базе данных. Это позволяет легко обращаться к этим настройкам через экземпляр settings.
  2. Гибкость: Теперь все настройки для базы данных находятся в одном месте, и вы можете легко добавлять новые настройки или изменять существующие, просто обновив класс Settings.
  3. Использование настроек: Используя экземпляр settings, вы можете удобно получать доступ ко всем параметрам приложения, включая параметры подключения к базе данных, без необходимости напрямую взаимодействовать с .env файлом.

 

Заключение

Продумывание архитектуры приложения – крайне важная задача, которой часто пренебрегают, что может привести к трудностям в будущем. В нашем случае мы подошли к проектированию с осознанием всех ключевых аспектов, таких как безопасность данных, удобство работы с настройками и поддержка асинхронности для более эффективной работы с базой данных. Мы учли лучшие практики и применили надёжные библиотеки для упрощения разработки и её масштабируемости.

Использование .env файла для хранения конфиденциальных данных и pydantic-settings для управления настройками позволяет нам централизованно и безопасно работать с конфигурацией проекта. Мы также внедрили поддержку асинхронной работы с базой данных с использованием SQLAlchemy и asyncpg, что улучшает производительность и отклик приложения.

Репозиторий проекта в GitHub: https://github.com/proDreams/lkeep 
Репозиторий проекта в "GIT на салфетке": https://git.pressanybutton.ru/proDream/lkeep

Автор

    Нет комментариев

    Реклама