pydantic-settings skill
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user