From 0177496fab246cb50335a62fcf0aa7647f493803 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:51:49 -0500 Subject: [PATCH] pydantic-settings skill --- docs/skills/pydantic-settings/SKILL.md | 310 +++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 docs/skills/pydantic-settings/SKILL.md diff --git a/docs/skills/pydantic-settings/SKILL.md b/docs/skills/pydantic-settings/SKILL.md new file mode 100644 index 0000000..feb8fda --- /dev/null +++ b/docs/skills/pydantic-settings/SKILL.md @@ -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)