Skip to content
AB

Fail Fast with Pydantic Settings

Validate environment variables, production transport security, and operational limits once during startup.

Ashish BhakhandPublished

Scattered os.getenv() calls turn configuration mistakes into request-time failures. A missing database URL might remain invisible until the first user hits a particular endpoint.

I prefer one validated settings object built at startup:

Code
class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file='.env',
        extra='ignore',
    )

    environment: Environment = Field(
        default=Environment.development,
        alias='ENVIRONMENT',
    )
    database_url: str = Field(alias='DATABASE_URL')
    api_secret: str = Field(alias='API_SECRET')
    max_request_bytes: int = Field(default=1_000_000, ge=0)

Required values have no fake defaults. If they are absent, the process refuses to start.

Validate relationships between settings#

Production has stronger requirements than local development:

Code
@model_validator(mode='after')
def validate_production_security(self) -> Self:
    if self.environment is not Environment.production:
        return self

    if not self.database_url.startswith('postgresql://'):
        raise ValueError('Production requires a PostgreSQL URL')
    if 'sslmode=' not in self.database_url:
        raise ValueError('Production database connections require TLS')
    return self

This converts a deployment convention into executable policy.

Normalize operational input#

Platforms encode list values differently. A validator can accept either a JSON array or comma-separated string for CORS origins while exposing one list[str] to the application.

Be careful with booleans and URLs. “Any non-empty string” is not a safe boolean parser, and checking that a URL merely starts with https is weaker than parsing its scheme.

Build once and inject#

Code
@lru_cache(maxsize=1)
def get_settings() -> Settings:
    return Settings()

FastAPI dependencies can return this cached object. Tests override the dependency or clear the cache before constructing settings with a different environment.

Settings validation should establish that the application can operate safely. Feature code may still check whether optional integrations are enabled, but it should never discover fundamental configuration errors halfway through serving traffic.

← Back to all writing