Settings and Configuration
Introduction
Production apps read secrets and options from the environment, not hardcoded strings. Pydantic Settings loads and validates configuration from .env files and environment variables. This chapter sets up Settings, exposes them via Depends, and separates development from production values.
Prerequisites
- Dependency Injection
pip install pydantic-settings python-dotenv
Define Settings
app/core/config.py:
Code explanation:
BaseSettingsreads fields from environment (case-insensitive)env_file=".env"loads local file for development@lru_cacheonget_settingsavoids reparsing env every request
.env File
Create .env (gitignored):
APP_NAME=Hello FastAPI
ENVIRONMENT=development
DEBUG=true
SECRET_KEY=your-long-random-string
DATABASE_URL=sqlite:///./dev.db
ACCESS_TOKEN_EXPIRE_MINUTES=60Field names map to env vars: secret_key → SECRET_KEY.
.env.example for teammates:
SECRET_KEY=replace-me
DATABASE_URL=sqlite:///./app.db
DEBUG=falseWarning
Never Commit .env
Rotate secrets if they ever leak into Git history.
See Git .gitignore and Flask configuration.
Inject Settings in Routes
from fastapi import Depends, FastAPI
from app.core.config import Settings, get_settings
app = FastAPI()
@app.get("/info")
def app_info(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"debug": settings.debug,
}Do not return secret_key to clients.
Environment-Specific Values
Option 1 — separate env files:
model_config = SettingsConfigDict(env_file=".env.production")Option 2 — use the environment field on Settings (see above):
Deploy production with:
export ENVIRONMENT=production
export SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
export DATABASE_URL="mysql+pymysql://user:pass@localhost/app?charset=utf8mb4"Validate Configuration at Startup
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
if settings.is_production and settings.secret_key.startswith("dev-"):
raise RuntimeError("Set a strong SECRET_KEY in production")
yield
app = FastAPI(lifespan=lifespan)Fail fast on misconfiguration before accepting traffic.
Testing Settings
from app.core.config import Settings, get_settings
def get_settings_override():
return Settings(
secret_key="test-secret",
database_url="sqlite:///:memory:",
debug=True,
)
app.dependency_overrides[get_settings] = get_settings_overrideOr set env vars before importing app in pytest—see testing chapter.
Common Settings Fields
| Field | Purpose |
|---|---|
secret_key | Signing JWT and sessions |
database_url | SQLAlchemy engine URL |
cors_origins | List of allowed frontend origins (parse as JSON or comma-separated) |
openapi_url | Set None to disable /docs in production |
Parse list from env:
from pydantic import field_validator
class Settings(BaseSettings):
cors_origins: list[str] = ["http://localhost:5173"]
@field_validator("cors_origins", mode="before")
@classmethod
def split_origins(cls, v):
if isinstance(v, str):
return [x.strip() for x in v.split(",") if x.strip()]
return vEnv: CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
FAQ
Settings not loading from .env?
Ensure env_file path is correct relative to working directory when starting Uvicorn.
Field name vs env var?
Pydantic Settings accepts SECRET_KEY for secret_key automatically.
Override .env with export?
Environment variables take precedence over .env file values.
pydantic-settings version?
Use with Pydantic v2—pin compatible versions in requirements.txt.
Store JSON in env?
Use model_validate_json or parse in validator—keep secrets out of repo.
Same as Flask config classes?
Same 12-factor idea—Flask uses dict/classes; FastAPI uses typed Settings.