Cheatsheet for typed config via pydantic-settings.

Install

uv add pydantic-settings

Basic

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    log_level: str = "INFO"
    
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        env_prefix="MYAPP_",
        case_sensitive=False,
    )

settings = Settings()

Reads MYAPP_DATABASE_URL, MYAPP_SECRET_KEY, etc.

.env file

# .env (don't commit)
MYAPP_DATABASE_URL=postgresql://...
MYAPP_SECRET_KEY=...
MYAPP_LOG_LEVEL=DEBUG

Commit .env.example instead.

Nested settings

class DBSettings(BaseSettings):
    url: str
    pool_size: int = 10
    
    model_config = SettingsConfigDict(env_prefix="MYAPP_DB_")

class Settings(BaseSettings):
    db: DBSettings = DBSettings()
    secret_key: str
    
    model_config = SettingsConfigDict(env_prefix="MYAPP_")
MYAPP_DB_URL=postgresql://...
MYAPP_DB_POOL_SIZE=20
MYAPP_SECRET_KEY=...

Nested via delimiter

class Settings(BaseSettings):
    db: DBSettings
    
    model_config = SettingsConfigDict(env_nested_delimiter="__")
MYAPP_DB__URL=postgresql://...

Secrets directory (Docker secrets)

class Settings(BaseSettings):
    secret_key: str
    
    model_config = SettingsConfigDict(secrets_dir="/run/secrets")

Looks for /run/secrets/secret_key (lowercase by default).

Multiple .env files

model_config = SettingsConfigDict(env_file=(".env", ".env.local"))

Later files override earlier.

Source priority

Default (highest first):

  1. CLI args (cli_parse_args=True).
  2. Settings(...) kwargs.
  3. Environment vars.
  4. Dotenv file.
  5. Secrets directory.
  6. Defaults.

SecretStr fields

from pydantic import SecretStr

class Settings(BaseSettings):
    api_key: SecretStr
    db_password: SecretStr

print(settings.api_key)                       # SecretStr('**********')
print(settings.api_key.get_secret_value())    # actual value

Prevents accidental log leaks.

CLI integration

class Settings(BaseSettings):
    database_url: str
    debug: bool = False
    
    model_config = SettingsConfigDict(cli_parse_args=True)

# python myapp.py --database-url=... --debug

For complex CLI: typer or click; for config: pydantic-settings.

TOML / YAML / JSON sources

from pydantic_settings import BaseSettings, TomlConfigSettingsSource

class Settings(BaseSettings):
    model_config = SettingsConfigDict(toml_file="config.toml")
    
    @classmethod
    def settings_customise_sources(cls, settings_cls, *args):
        return (TomlConfigSettingsSource(settings_cls),) + args

Similar for YamlConfigSettingsSource, JsonConfigSettingsSource.

With FastAPI

from functools import lru_cache
from fastapi import Depends

@lru_cache
def get_settings() -> Settings:
    return Settings()

@app.get("/info")
async def info(s: Settings = Depends(get_settings)):
    return {"db": s.database_url[:20] + "..."}

lru_cache ensures single Settings instance.

Per-env config

class Settings(BaseSettings):
    env: Literal["dev", "staging", "prod"] = "dev"
    database_url: str
    
    @model_validator(mode="after")
    def prod_safety(self):
        if self.env == "prod" and "localhost" in self.database_url:
            raise ValueError("prod can't use localhost DB")
        return self

Type validation at startup

try:
    settings = Settings()
except ValidationError as e:
    print(e)
    sys.exit(1)

Fail fast. Don’t start with bad config.

Frozen settings

class Settings(BaseSettings):
    model_config = SettingsConfigDict(frozen=True)

Immutable after creation.

Common mistakes

  • Plaintext .env in git — add to .gitignore; commit .env.example.
  • Settings() without lru_cache — reads .env every call.
  • Forgetting case_sensitive=FalseDATABASE_URL vs database_url mismatch.
  • SecretStr in logs without redaction — print(settings) works (redacted); model_dump() doesn’t.
  • Mixed env_prefix conventions across services.

Read this next

If you want my Settings template with secrets + validation, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .