Pydantic Settings Fundamentals

A BaseSettings subclass is the single object that should own every configuration read in a Python service. It pulls from the environment, applies types, runs validators, and either constructs cleanly or raises a ValidationError that stops the process before it serves traffic. This page builds that object correctly with pydantic-settings v2.

This is the foundation of the type-safe validation section — it turns the raw configuration sources into a typed contract the rest of the application can depend on.

Secure implementation

# config/settings.py
from functools import lru_cache
from pydantic import SecretStr, Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        env_nested_delimiter="__",   # CACHE__HOST -> cache.host
        extra="forbid",              # unknown vars raise instead of being ignored
        case_sensitive=False,
    )

    database_url: str
    api_key: SecretStr               # masked in repr(), model_dump(), and logs
    workers: int = Field(default=4, ge=1, le=64)
    debug: bool = False


@lru_cache
def get_settings() -> Settings:
    return Settings()                # constructed once; raises at startup on error

extra="forbid" makes a misspelled variable fatal. SecretStr keeps the API key out of any serialized output. lru_cache means the model is built exactly once and shared.

Configuration reference

Setting Type Default Security implication
extra "forbid"/"ignore"/"allow" "ignore" "forbid" catches typos and injected junk
env_nested_delimiter str None Enables nested models from flat env vars
case_sensitive bool False Match your platform’s env-var casing
SecretStr field masked Prevents secret leakage in logs
env_file str None Local convenience; below real env vars

Deployment parity: local to production

  1. Local dev.env supplies values; get_settings() validates them on first call.
  2. CI — instantiate Settings() in a test; a missing or malformed key fails the build.
  3. Staging/Production — the orchestrator injects environment variables that outrank the (absent) .env; the identical model validates them at boot.

Security boundaries & guardrails

  • Always set extra="forbid"; silent acceptance of unknown variables hides configuration drift.
  • Wrap every credential field in SecretStr; assert in a test that repr(settings) contains no secret.
  • Keep one settings class per service — no per-environment subclasses with diverging fields.
  • Use SettingsConfigDict, not the deprecated inner class Config.

Troubleshooting

  • ValidationError at startup — a required field is missing or malformed; the message names the exact field. This is the intended fail-fast behaviour.
  • Nested model not populatedenv_nested_delimiter is unset or the variable uses the wrong delimiter (CACHE__HOST, not CACHE_HOST). See Nested Settings Models in Pydantic.
  • Secret printed in logs — the field is a plain str; change it to SecretStr.
  • Migrating from v1 — inner class Config and BaseSettings from pydantic moved; see Migrate to pydantic-settings v2.

Frequently asked questions

How do I configure a BaseSettings model in pydantic v2?

Use model_config = SettingsConfigDict(...) on the class. The legacy inner class Config from v1 still partly works but raises deprecation warnings; SettingsConfigDict is the supported v2 API.

In what order does pydantic-settings read sources?

By default, init arguments win, then OS environment variables, then the .env file, then file secrets. Environment variables outrank the .env file, giving you correct production parity automatically.

How do I reject unknown environment variables?

Set extra="forbid" in SettingsConfigDict. A typo’d or stray variable then raises a ValidationError at startup instead of being silently ignored.

Conclusion

The invariant: one BaseSettings model, extra="forbid", secrets as SecretStr, constructed once and validated at startup. Everything else in this section extends this object.