os.getenv(), but it has a fundamental flaw: environment variables are always strings, and you have to manually parse them into the right types yourself. Miss a conversion and PORT=8000 stays a string, or DEBUG=False evaluates to True because any non-empty string is truthy. Pydantic Settings solves this by bringing the same runtime validation you’ve been using for request data to your application’s configuration — you declare what your settings look like, Pydantic reads the environment and validates everything for you, and your code works with typed, validated values from the start.
The problem with raw environment variables
Installation
pydantic-settings is a separate package (it ships separately from Pydantic v2):
Basic usage
Inherit fromBaseSettings and declare fields exactly as you would with BaseModel:
Settings(), Pydantic automatically reads your environment variables (case-insensitive):
1
API_KEY
Required — no default. If not set, Pydantic raises a
ValidationError immediately.2
PORT
Optional — defaults to
8000. If set, the string is automatically parsed to int.3
DEBUG
Optional — defaults to
False. Accepts "True", "False", "1", "0", "yes", "no".Reading from a .env file
In development, store your configuration in a .env file rather than setting shell variables by hand:
SettingsConfigDict:
Environment variables set in your shell always override values in
.env. This is intentional — your CI/CD pipeline or production host sets real secrets via environment variables, while .env provides convenient local defaults.Using an environment prefix
If your host environment has many variables from different applications, prefix your settings to avoid collisions:Protecting secrets with SecretStr
Use SecretStr for sensitive values like passwords and API keys. Pydantic masks the value when the object is printed or logged, so secrets don’t leak into your output:
Caching settings with lru_cache
Instantiating Settings() reads files and environment variables. Call it once and reuse the result everywhere via lru_cache:
get_settings as a dependency:
Environment-specific behaviour
Add computed properties to your settings class for environment-aware logic:A complete example
Here’s a realistic settings class for a FastAPI application:.env file:
Why not just use a plain dict or config.py file?
Why not just use a plain dict or config.py file?
A plain dictionary or module-level constants give you no validation — a typo in a variable name silently returns
None, and environment variable parsing is manual and error-prone. BaseSettings gives you:- Automatic type coercion — strings become ints, bools, lists
- Validation errors for missing required settings at startup, not at runtime
- Secret masking via
SecretStr - Clear documentation of every setting your app needs
- Overridable in tests — you can pass values directly:
Settings(api_key="test-key")
Learn more
What’s next?
You now have a complete Pydantic toolkit — models, field constraints, nested structures, and settings. The next step is putting all of this to work inside a FastAPI application.Project Handling
Learn how to structure Python projects, manage dependencies, and package your applications.