311 lines
9.3 KiB
Markdown
311 lines
9.3 KiB
Markdown
---
|
|
name: pydantic-settings
|
|
description: "Practical guide for implementing typed application configuration with pydantic-settings. Use when designing BaseSettings models, choosing env naming strategy, configuring dotenv or secrets, and customizing source priority safely."
|
|
x-personal-mcp:
|
|
id: pydantic-settings
|
|
version: 1.0.0
|
|
tags:
|
|
- python
|
|
- pydantic
|
|
- pydantic-settings
|
|
- configuration
|
|
- env-vars
|
|
- secrets
|
|
- dotenv
|
|
- source-priority
|
|
capabilities:
|
|
- resource://skills/pydantic-settings/document
|
|
---
|
|
|
|
# Pydantic Settings Implementation Guide
|
|
|
|
Use this skill to implement robust, typed application configuration with `pydantic-settings` in production Python services.
|
|
|
|
## When to Use
|
|
|
|
- You need a single typed configuration model for app settings.
|
|
- You are migrating from ad-hoc `os.getenv(...)` calls.
|
|
- You need predictable precedence across init args, env vars, dotenv files, and secrets.
|
|
- You need nested settings models and reliable parsing behavior.
|
|
- You need to customize settings sources or source order safely.
|
|
|
|
## Procedure
|
|
|
|
### 1. Baseline Model
|
|
|
|
Create a single settings model for the service boundary:
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class DatabaseSettings(BaseModel):
|
|
host: str = "localhost"
|
|
port: int = 5432
|
|
user: str
|
|
password: str
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="APP_",
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
)
|
|
|
|
debug: bool = False
|
|
log_level: str = "info"
|
|
database: DatabaseSettings
|
|
api_key: str = Field(validation_alias="MY_API_KEY")
|
|
```
|
|
|
|
Quality gate:
|
|
|
|
1. Required fields fail fast when missing.
|
|
2. Defaults are intentional and safe.
|
|
|
|
### 2. Pick Env Naming Rules
|
|
|
|
1. Choose one prefix and apply it consistently.
|
|
2. Use aliases only for compatibility or external contracts.
|
|
3. Document whether env names are case-sensitive.
|
|
|
|
Quality gate:
|
|
|
|
1. Team can derive env variable names without guessing.
|
|
2. Legacy names are supported only where needed.
|
|
|
|
### 3. Decide Nested Parsing
|
|
|
|
For nested models via env vars, configure delimiters intentionally:
|
|
|
|
```python
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="APP_",
|
|
env_nested_delimiter="__",
|
|
env_nested_max_split=1,
|
|
)
|
|
```
|
|
|
|
Typical vars:
|
|
|
|
1. `APP_DATABASE={"host": "db", "port": 5432, "user": "svc", "password": "pw"}`
|
|
2. `APP_DATABASE__HOST=db.internal`
|
|
|
|
Quality gate:
|
|
|
|
1. Nested overrides behave as expected.
|
|
2. Delimiter choice does not collide with field names.
|
|
|
|
### 4. Confirm Source Priority
|
|
|
|
Default priority (higher first):
|
|
|
|
1. CLI args (if enabled)
|
|
2. init kwargs
|
|
3. env vars
|
|
4. dotenv
|
|
5. secrets dir
|
|
6. defaults
|
|
|
|
Only customize when required:
|
|
|
|
```python
|
|
from pydantic_settings import PydanticBaseSettingsSource
|
|
|
|
|
|
@classmethod
|
|
def settings_customise_sources(
|
|
cls,
|
|
settings_cls: type[BaseSettings],
|
|
init_settings: PydanticBaseSettingsSource,
|
|
env_settings: PydanticBaseSettingsSource,
|
|
dotenv_settings: PydanticBaseSettingsSource,
|
|
file_secret_settings: PydanticBaseSettingsSource,
|
|
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
return (init_settings, env_settings, dotenv_settings, file_secret_settings)
|
|
```
|
|
|
|
Quality gate:
|
|
|
|
1. Priority order is explicit in code.
|
|
2. Tests verify conflict resolution.
|
|
|
|
### 5. Add Secrets Strategy
|
|
|
|
1. In local development, dotenv is acceptable for non-production values.
|
|
2. In deployed environments, prefer env vars or secret managers.
|
|
3. For file-mounted secrets, use `secrets_dir`.
|
|
|
|
Example:
|
|
|
|
```python
|
|
model_config = SettingsConfigDict(
|
|
env_prefix="APP_",
|
|
env_file=".env",
|
|
secrets_dir="/run/secrets",
|
|
)
|
|
```
|
|
|
|
Quality gate:
|
|
|
|
1. No secret literals in repository code.
|
|
2. Missing secrets behavior is understood per environment.
|
|
|
|
### 6. Add ContextVar-Scoped Constructors And Accessors
|
|
|
|
When configuration and database resources should be request- or context-scoped, use `ContextVar` backed constructor and accessor methods.
|
|
|
|
Example pattern:
|
|
|
|
```python
|
|
from contextlib import contextmanager
|
|
from contextvars import ContextVar
|
|
from functools import cache
|
|
|
|
from pydantic import SecretStr
|
|
from pydantic_settings import BaseSettings
|
|
from sqlmodel import Session, create_engine
|
|
from sqlalchemy import Engine
|
|
|
|
|
|
class DbSettings(BaseSettings):
|
|
model_config = {
|
|
"env_prefix": "DB_",
|
|
"extra": "ignore",
|
|
}
|
|
|
|
host: str = "localhost"
|
|
port: int = 5432
|
|
username: str
|
|
password: SecretStr
|
|
|
|
@property
|
|
def dsn(self) -> str:
|
|
return (
|
|
"postgresql://"
|
|
f"{self.username}:{self.password.get_secret_value()}"
|
|
f"@{self.host}:{self.port}/mydatabase"
|
|
)
|
|
|
|
|
|
_db_settings: ContextVar[DbSettings | None] = ContextVar("db_settings", default=None)
|
|
_db_conn: ContextVar[Engine | None] = ContextVar("db_conn", default=None)
|
|
|
|
|
|
def get_db_settings(**kwargs) -> DbSettings:
|
|
settings = _db_settings.get()
|
|
if settings is None:
|
|
settings = DbSettings(**kwargs)
|
|
_db_settings.set(settings)
|
|
cleanup_engine()
|
|
return settings
|
|
|
|
|
|
@cache
|
|
def get_db_engine() -> Engine:
|
|
engine = _db_conn.get()
|
|
if engine is None:
|
|
engine = create_engine(get_db_settings().dsn)
|
|
_db_conn.set(engine)
|
|
return engine
|
|
|
|
|
|
def cleanup_engine() -> None:
|
|
engine = _db_conn.get()
|
|
if engine is not None:
|
|
engine.dispose()
|
|
_db_conn.set(None)
|
|
get_db_engine.cache_clear()
|
|
|
|
|
|
@contextmanager
|
|
def get_session():
|
|
with Session(get_db_engine()) as session:
|
|
yield session
|
|
```
|
|
|
|
Design notes:
|
|
|
|
1. `get_db_settings` is the constructor/accessor for settings and can accept explicit overrides in tests.
|
|
2. `get_db_engine` is the constructor/accessor for the engine and reuses context-local state.
|
|
3. `cleanup_engine` must run when settings change so stale DSNs do not leak across contexts.
|
|
4. `get_session` centralizes session creation so call sites never build engines directly.
|
|
|
|
Quality gate:
|
|
|
|
1. Overriding settings triggers engine cleanup and cache invalidation.
|
|
2. No module-level global engine is created outside accessors.
|
|
3. Session creation always goes through `get_session()`.
|
|
|
|
### 7. Add Focused Resource-Lifecycle Test
|
|
|
|
Do not add tests that re-validate baseline `pydantic-settings` functionality (for example env parsing, alias semantics, or source precedence) unless you have custom behavior layered on top.
|
|
|
|
Minimum test to add (only when an engine accessor exists):
|
|
|
|
1. assert the database engine is not instantiated more than once for repeated accessor calls in the same lifecycle/context
|
|
|
|
If the project has no database engine accessor, skip this section.
|
|
|
|
Suggested invocation:
|
|
|
|
1. `uv run pytest -q`
|
|
|
|
## Completion Checks
|
|
|
|
1. A single typed settings model exists for the service boundary.
|
|
2. Source precedence is documented and tested.
|
|
3. Env naming conventions and aliases are explicit and stable.
|
|
4. Nested parsing behavior is tested when custom parsing behavior is added.
|
|
5. Secrets and dotenv usage are environment-appropriate and do not leak sensitive defaults.
|
|
6. Validation errors are actionable and fail fast for required values.
|
|
7. If an engine accessor exists, engine construction occurs at most once per lifecycle/context.
|
|
|
|
## Output Contract
|
|
|
|
When this skill is applied, return:
|
|
|
|
1. Which references were consulted.
|
|
2. The chosen source-precedence model and why.
|
|
3. The exact parsing and alias decisions made.
|
|
4. Any deferred choices and their risk.
|
|
5. The validation commands or tests run to confirm behavior.
|
|
|
|
Use these upstream docs when implementing or reviewing `pydantic-settings` behavior.
|
|
|
|
## Source Docs
|
|
|
|
### Primary
|
|
|
|
- [Settings Management](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/)
|
|
- [pydantic-settings package repository](https://github.com/pydantic/pydantic-settings)
|
|
|
|
### Core Concepts
|
|
|
|
- [Field aliases](https://pydantic.dev/docs/validation/latest/concepts/fields/#field-aliases)
|
|
- [Alias choices](https://pydantic.dev/docs/validation/latest/concepts/alias#aliaspath-and-aliaschoices)
|
|
- [Validation default behavior](https://pydantic.dev/docs/validation/latest/concepts/fields#validate-default-values)
|
|
- [ImportString type](https://pydantic.dev/docs/validation/latest/api/pydantic/types/#pydantic.types.ImportString)
|
|
|
|
### Priority And Sources
|
|
|
|
- [Field value priority](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#field-value-priority)
|
|
- [Customise settings sources](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#customise-settings-sources)
|
|
- [Other settings source types](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#other-settings-source)
|
|
|
|
### Environment And Parsing
|
|
|
|
- [Environment variable names and prefix behavior](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#environment-variable-names)
|
|
- [Case sensitivity behavior](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#case-sensitivity)
|
|
- [Parsing environment variable values](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#parsing-environment-variable-values)
|
|
- [Nested model default partial updates](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#nested-model-default-partial-updates)
|
|
|
|
### Dotenv And Secrets
|
|
|
|
- [Dotenv support](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#dotenv-env-support)
|
|
- [Secrets](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#secrets)
|
|
- [Nested secrets](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#nested-secrets)
|