pydantic-settings skill

This commit is contained in:
John Lancaster
2026-06-25 21:51:49 -05:00
parent 00498a2fed
commit 0177496fab
+310
View File
@@ -0,0 +1,310 @@
---
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)