from functools import lru_cache from pydantic import AnyHttpUrl, Field, SecretStr, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=None, extra="ignore") database_url: str = Field(alias="SMS_DATABASE_URL") service_token: SecretStr = Field(alias="SMS_SERVICE_TOKEN", min_length=32) idgtl_base_url: AnyHttpUrl = Field( default=AnyHttpUrl("https://direct.i-dgtl.ru"), alias="IDGTL_SMS_BASE_URL" ) idgtl_api_key: SecretStr | None = Field(default=None, alias="IDGTL_SMS_API_KEY") callback_public_url: AnyHttpUrl = Field(alias="IDGTL_SMS_CALLBACK_PUBLIC_URL") callback_username: SecretStr = Field(alias="IDGTL_SMS_CALLBACK_USERNAME") callback_password: SecretStr = Field(alias="IDGTL_SMS_CALLBACK_PASSWORD") log_level: str = Field(default="INFO", alias="LOG_LEVEL") api_port: int = Field(default=8080, alias="SMS_API_PORT", ge=1, le=65535) metrics_port: int = Field(default=9464, alias="SMS_METRICS_PORT", ge=1, le=65535) @field_validator( "service_token", "callback_username", "callback_password", ) @classmethod def reject_placeholders(cls, value: SecretStr) -> SecretStr: raw = value.get_secret_value().strip() if not raw or raw.lower() in {"changeme", "secret", "token", ""}: raise ValueError("secret is missing or is a placeholder") return value @field_validator("idgtl_api_key") @classmethod def reject_api_key_placeholder(cls, value: SecretStr | None) -> SecretStr | None: if value is None: return None return cls.reject_placeholders(value) @field_validator("callback_public_url") @classmethod def callback_must_be_https(cls, value: AnyHttpUrl) -> AnyHttpUrl: if value.scheme != "https": raise ValueError("callback URL must use HTTPS") if value.username or value.password: raise ValueError("callback URL must not contain credentials") return value @lru_cache def get_settings() -> Settings: return Settings()