Короткий ответ
Создаётся класс, наследующий BaseSettings, с полями-настройками и model_config = SettingsConfigDict(env_file='.env'); переменные окружения процесса по умолчанию имеют приоритет над значениями из .env-файла.
Как это работает подробнее
BaseSettings при инициализации читает значения в следующем порядке приоритета (для pydantic-settings v2): явно переданные аргументы, переменные окружения, значения из .env, значения по умолчанию в классе. Такой порядок позволяет держать .env для локальной разработки и переопределять конкретные значения через окружение в CI/production без изменения кода.
Пример кода
example.py
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
database_url: str
debug: bool = False
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
@lru_cache
def get_settings() -> Settings:
return Settings()
# .env: DATABASE_URL=postgresql://localhost/app
# В окружении CI: DATABASE_URL=postgresql://ci-host/app -> имеет приоритет над .envЧто использовать на практике
- Практика. Настройки инстанцируют один раз (например, через
@lru_cacheна функцииget_settings()) и внедряют в эндпоинты черезDepends(get_settings), что упрощает подмену конфигурации в тестах черезdependency_overrides.
Подводные камни
- Риск. Коммитят
.envс реальными секретами в репозиторий; создаютSettings()прямо на уровне модуля при каждом импорте без кэширования, из-за чего файл.envчитается многократно и сложнее подменяется в тестах.