PydanticMiddleCoding
Как валидировать переменные окружения с помощью pydantic-settings?
pydantic-settings предоставляет BaseSettings — подкласс BaseModel, читающий переменные окружения и .env-файлы. Поля автоматически валидируются при старте приложения.
Установка и базовое использование
pydantic-settings — отдельный пакет, не входящий в pydantic по умолчанию.
# pip install pydantic-settings
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, SecretStr
from typing import Optional
class AppSettings(BaseSettings):
# Переменные окружения читаются автоматически (case-insensitive)
app_name: str = "MyApp"
debug: bool = False
port: int = Field(default=8000, ge=1024, le=65535)
# Чувствительные данные — SecretStr (не попадут в repr)
secret_key: SecretStr
database_url: str
redis_url: str = "redis://localhost:6379"
# Опциональные внешние сервисы
sentry_dsn: Optional[str] = None
model_config = SettingsConfigDict(
env_file=".env", # читать .env файл
env_file_encoding="utf-8",
case_sensitive=False, # DATABASE_URL == database_url
extra="ignore", # игнорировать лишние переменные
)
# Создаётся один раз при старте — автоматически читает окружение
settings = AppSettings()
print(settings.app_name)
print(settings.secret_key.get_secret_value()) # явное раскрытие
Файл .env и приоритет источников
# .env файл
APP_NAME=Production App
DEBUG=false
DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/mydb
SECRET_KEY=super-secret-key-here
Приоритет источников (от высшего к низшему):
- Аргументы конструктора:
AppSettings(debug=True) - Переменные окружения (environment variables)
- Значения из
.envфайла - Default-значения полей
Префиксы для группировки настроек
from pydantic_settings import BaseSettings, SettingsConfigDict
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
name: str = "app"
user: str
password: SecretStr
model_config = SettingsConfigDict(
env_prefix="DB_", # DB_HOST, DB_PORT, DB_NAME, ...
env_file=".env",
)
class RedisSettings(BaseSettings):
host: str = "localhost"
port: int = 6379
db: int = 0
model_config = SettingsConfigDict(
env_prefix="REDIS_",
env_file=".env",
)
class Settings(BaseSettings):
database: DatabaseSettings = DatabaseSettings()
redis: RedisSettings = RedisSettings()
secret_key: SecretStr
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
print(settings.database.host)
Singleton через lru_cache (FastAPI-паттерн)
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import SecretStr, PostgresDsn
class Settings(BaseSettings):
database_url: PostgresDsn # встроенная валидация URL
secret_key: SecretStr
allowed_hosts: list[str] = ["localhost"]
debug: bool = False
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
)
@lru_cache
def get_settings() -> Settings:
return Settings()
# В FastAPI endpoint:
from fastapi import Depends
def some_endpoint(settings: Settings = Depends(get_settings)):
db_url = str(settings.database_url)
return {"db": db_url}
Валидация списков и сложных типов из env
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import List
class Settings(BaseSettings):
# ALLOWED_HOSTS=["host1","host2"] или ALLOWED_HOSTS=host1,host2
allowed_hosts: List[str] = ["localhost"]
# FEATURE_FLAGS={"new_ui": true, "beta": false}
feature_flags: dict = {}
model_config = SettingsConfigDict(
env_file=".env",
)
# В .env:
# ALLOWED_HOSTS=["api.example.com","www.example.com"]
# FEATURE_FLAGS={"new_ui": true}
Подводные камни
- SecretStr в логах —
repr(settings)показываетSecretStr('**********'), ноsettings.model_dump()возвращает сам объектSecretStr. Для сериализации в JSON используйтеmodel_dump(mode='json')— тогда значение будет скрыто как"**********". - .env не читается в тестах автоматически — pytest не подгружает
.env; используйтеpython-dotenvили monkey-patching черезmonkeypatch.setenv(). Лучше передавайте настройки явно:Settings(secret_key="test"). - Приоритет env над .env — если переменная уже задана в окружении CI/CD, значение из
.env-файла будет проигнорировано. Это частая причина «работает локально, не работает в CI». - env_prefix применяется ко всем полям — при использовании
env_prefix="DB_"даже поля с именемurlтребуют переменнуюDB_URL, неURL. - Вложенные BaseSettings не наследуют .env родителя — каждый вложенный класс читает свой
.envнезависимо; передайте путь явно черезSettingsConfigDict(env_file=...)в каждом классе. - PostgresDsn хранится как объект, не str —
str(settings.database_url)необходимо для передачи в SQLAlchemycreate_engine().
Common mistakes
- Описывать settings env validation только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Pydantic и реальной эксплуатацией.
What the interviewer is testing
- Объясняет settings env validation через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.