Claude Agent Skill · by Wshobson

Python Configuration

Solid foundation for externalizing Python app configuration using pydantic-settings and environment variables. Sets up typed configuration classes that validate

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill python-configuration
Works with Paperclip

How Python Configuration fits into a Paperclip company.

Python Configuration drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md368 lines
Expand
---name: python-configurationdescription: Python configuration management via environment variables and typed settings. Use when externalizing config, setting up pydantic-settings, managing secrets, or implementing environment-specific behavior.--- # Python Configuration Management Externalize configuration from code using environment variables and typed settings. Well-managed configuration enables the same code to run in any environment without modification. ## When to Use This Skill - Setting up a new project's configuration system- Migrating from hardcoded values to environment variables- Implementing pydantic-settings for typed configuration- Managing secrets and sensitive values- Creating environment-specific settings (dev/staging/prod)- Validating configuration at application startup ## Core Concepts ### 1. Externalized Configuration All environment-specific values (URLs, secrets, feature flags) come from environment variables, not code. ### 2. Typed Settings Parse and validate configuration into typed objects at startup, not scattered throughout code. ### 3. Fail Fast Validate all required configuration at application boot. Missing config should crash immediately with a clear message. ### 4. Sensible Defaults Provide reasonable defaults for local development while requiring explicit values for sensitive settings. ## Quick Start ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Field class Settings(BaseSettings):    database_url: str = Field(alias="DATABASE_URL")    api_key: str = Field(alias="API_KEY")    debug: bool = Field(default=False, alias="DEBUG") settings = Settings()  # Loads from environment``` ## Fundamental Patterns ### Pattern 1: Typed Settings with Pydantic Create a central settings class that loads and validates all configuration. ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Field, PostgresDsn, ValidationErrorimport sys class Settings(BaseSettings):    """Application configuration loaded from environment variables."""     # Database    db_host: str = Field(alias="DB_HOST")    db_port: int = Field(default=5432, alias="DB_PORT")    db_name: str = Field(alias="DB_NAME")    db_user: str = Field(alias="DB_USER")    db_password: str = Field(alias="DB_PASSWORD")     # Redis    redis_url: str = Field(default="redis://localhost:6379", alias="REDIS_URL")     # API Keys    api_secret_key: str = Field(alias="API_SECRET_KEY")     # Feature flags    enable_new_feature: bool = Field(default=False, alias="ENABLE_NEW_FEATURE")     model_config = {        "env_file": ".env",        "env_file_encoding": "utf-8",    } # Create singleton instance at module loadtry:    settings = Settings()except ValidationError as e:    print(f"Configuration error:\n{e}")    sys.exit(1)``` Import `settings` throughout your application: ```pythonfrom myapp.config import settings def get_database_connection():    return connect(        host=settings.db_host,        port=settings.db_port,        database=settings.db_name,    )``` ### Pattern 2: Fail Fast on Missing Configuration Required settings should crash the application immediately with a clear error. ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Field, ValidationErrorimport sys class Settings(BaseSettings):    # Required - no default means it must be set    api_key: str = Field(alias="API_KEY")    database_url: str = Field(alias="DATABASE_URL")     # Optional with defaults    log_level: str = Field(default="INFO", alias="LOG_LEVEL") try:    settings = Settings()except ValidationError as e:    print("=" * 60)    print("CONFIGURATION ERROR")    print("=" * 60)    for error in e.errors():        field = error["loc"][0]        print(f"  - {field}: {error['msg']}")    print("\nPlease set the required environment variables.")    sys.exit(1)``` A clear error at startup is better than a cryptic `None` failure mid-request. ### Pattern 3: Local Development Defaults Provide sensible defaults for local development while requiring explicit values for secrets. ```pythonclass Settings(BaseSettings):    # Has local default, but prod will override    db_host: str = Field(default="localhost", alias="DB_HOST")    db_port: int = Field(default=5432, alias="DB_PORT")     # Always required - no default for secrets    db_password: str = Field(alias="DB_PASSWORD")    api_secret_key: str = Field(alias="API_SECRET_KEY")     # Development convenience    debug: bool = Field(default=False, alias="DEBUG")     model_config = {"env_file": ".env"}``` Create a `.env` file for local development (never commit this): ```bash# .env (add to .gitignore)DB_PASSWORD=local_dev_passwordAPI_SECRET_KEY=dev-secret-keyDEBUG=true``` ### Pattern 4: Namespaced Environment Variables Prefix related variables for clarity and easy debugging. ```bash# Database configurationDB_HOST=localhostDB_PORT=5432DB_NAME=myappDB_USER=adminDB_PASSWORD=secret # Redis configurationREDIS_URL=redis://localhost:6379REDIS_MAX_CONNECTIONS=10 # AuthenticationAUTH_SECRET_KEY=your-secret-keyAUTH_TOKEN_EXPIRY_SECONDS=3600AUTH_ALGORITHM=HS256 # Feature flagsFEATURE_NEW_CHECKOUT=trueFEATURE_BETA_UI=false``` Makes `env | grep DB_` useful for debugging. ## Advanced Patterns ### Pattern 5: Type Coercion Pydantic handles common conversions automatically. ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Field, field_validator class Settings(BaseSettings):    # Automatically converts "true", "1", "yes" to True    debug: bool = False     # Automatically converts string to int    max_connections: int = 100     # Parse comma-separated string to list    allowed_hosts: list[str] = Field(default_factory=list)     @field_validator("allowed_hosts", mode="before")    @classmethod    def parse_allowed_hosts(cls, v: str | list[str]) -> list[str]:        if isinstance(v, str):            return [host.strip() for host in v.split(",") if host.strip()]        return v``` Usage: ```bashALLOWED_HOSTS=example.com,api.example.com,localhostMAX_CONNECTIONS=50DEBUG=true``` ### Pattern 6: Environment-Specific Configuration Use an environment enum to switch behavior. ```pythonfrom enum import Enumfrom pydantic_settings import BaseSettingsfrom pydantic import Field, computed_field class Environment(str, Enum):    LOCAL = "local"    STAGING = "staging"    PRODUCTION = "production" class Settings(BaseSettings):    environment: Environment = Field(        default=Environment.LOCAL,        alias="ENVIRONMENT",    )     # Settings that vary by environment    log_level: str = Field(default="DEBUG", alias="LOG_LEVEL")     @computed_field    @property    def is_production(self) -> bool:        return self.environment == Environment.PRODUCTION     @computed_field    @property    def is_local(self) -> bool:        return self.environment == Environment.LOCAL # Usageif settings.is_production:    configure_production_logging()else:    configure_debug_logging()``` ### Pattern 7: Nested Configuration Groups Organize related settings into nested models. ```pythonfrom pydantic import BaseModelfrom pydantic_settings import BaseSettings class DatabaseSettings(BaseModel):    host: str = "localhost"    port: int = 5432    name: str    user: str    password: str class RedisSettings(BaseModel):    url: str = "redis://localhost:6379"    max_connections: int = 10 class Settings(BaseSettings):    database: DatabaseSettings    redis: RedisSettings    debug: bool = False     model_config = {        "env_nested_delimiter": "__",        "env_file": ".env",    }``` Environment variables use double underscore for nesting: ```bashDATABASE__HOST=db.example.comDATABASE__PORT=5432DATABASE__NAME=myappDATABASE__USER=adminDATABASE__PASSWORD=secretREDIS__URL=redis://redis.example.com:6379``` ### Pattern 8: Secrets from Files For container environments, read secrets from mounted files. ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Fieldfrom pathlib import Path class Settings(BaseSettings):    # Read from environment variable or file    db_password: str = Field(alias="DB_PASSWORD")     model_config = {        "secrets_dir": "/run/secrets",  # Docker secrets location    }``` Pydantic will look for `/run/secrets/db_password` if the env var isn't set. ### Pattern 9: Configuration Validation Add custom validation for complex requirements. ```pythonfrom pydantic_settings import BaseSettingsfrom pydantic import Field, model_validator class Settings(BaseSettings):    db_host: str = Field(alias="DB_HOST")    db_port: int = Field(alias="DB_PORT")    read_replica_host: str | None = Field(default=None, alias="READ_REPLICA_HOST")    read_replica_port: int = Field(default=5432, alias="READ_REPLICA_PORT")     @model_validator(mode="after")    def validate_replica_settings(self):        if self.read_replica_host and self.read_replica_port == self.db_port:            if self.read_replica_host == self.db_host:                raise ValueError(                    "Read replica cannot be the same as primary database"                )        return self``` ## Best Practices Summary 1. **Never hardcode config** - All environment-specific values from env vars2. **Use typed settings** - Pydantic-settings with validation3. **Fail fast** - Crash on missing required config at startup4. **Provide dev defaults** - Make local development easy5. **Never commit secrets** - Use `.env` files (gitignored) or secret managers6. **Namespace variables** - `DB_HOST`, `REDIS_URL` for clarity7. **Import settings singleton** - Don't call `os.getenv()` throughout code8. **Document all variables** - README should list required env vars9. **Validate early** - Check config correctness at boot time10. **Use secrets_dir** - Support mounted secrets in containers