Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Python configuration management via environment variables and typed settings. Use when externalizing config, setting up pydantic-settings, managing secrets, or implementing environment-specific behavior.
.claude/skills/dicklesworthstone-python-configuration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 108% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-19 | ✓→✗ | ▼ Worse | 99% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 151% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 72% | 0% |
Externalize configuration from code using environment variables and typed settings. Well-managed configuration enables the same code to run in any environment without modification.
All environment-specific values (URLs, secrets, feature flags) come from environment variables, not code.
Parse and validate configuration into typed objects at startup, not scattered throughout code.
Validate all required configuration at application boot. Missing config should crash immediately with a clear message.
Provide reasonable defaults for local development while requiring explicit values for sensitive settings.
pythonfrom pydantic_settings import BaseSettings from 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
Create a central settings class that loads and validates all configuration.
pythonfrom pydantic_settings import BaseSettings from pydantic import Field, PostgresDsn, ValidationError import 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 load try: 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, )
Required settings should crash the application immediately with a clear error.
pythonfrom pydantic_settings import BaseSettings from pydantic import Field, ValidationError import 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.
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_password API_SECRET_KEY=dev-secret-key DEBUG=true
Prefix related variables for clarity and easy debugging.
bash# Database configuration DB_HOST=localhost DB_PORT=5432 DB_NAME=myapp DB_USER=admin DB_PASSWORD=secret # Redis configuration REDIS_URL=redis://localhost:6379 REDIS_MAX_CONNECTIONS=10 # Authentication AUTH_SECRET_KEY=your-secret-key AUTH_TOKEN_EXPIRY_SECONDS=3600 AUTH_ALGORITHM=HS256 # Feature flags FEATURE_NEW_CHECKOUT=true FEATURE_BETA_UI=false
Makes env | grep DB_ useful for debugging.
Pydantic handles common conversions automatically.
pythonfrom pydantic_settings import BaseSettings from 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,localhost MAX_CONNECTIONS=50 DEBUG=true
Use an environment enum to switch behavior.
pythonfrom enum import Enum from pydantic_settings import BaseSettings from 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 # Usage if settings.is_production: configure_production_logging() else: configure_debug_logging()
Organize related settings into nested models.
pythonfrom pydantic import BaseModel from 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.com DATABASE__PORT=5432 DATABASE__NAME=myapp DATABASE__USER=admin DATABASE__PASSWORD=secret REDIS__URL=redis://redis.example.com:6379
For container environments, read secrets from mounted files.
pythonfrom pydantic_settings import BaseSettings from pydantic import Field from 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.
Add custom validation for complex requirements.
pythonfrom pydantic_settings import BaseSettings from 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
.env files (gitignored) or secret managersDB_HOST, REDIS_URL for clarityos.getenv() throughout code| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | 11,393 | 6,848 | -40% | 1 | 1 | 0% | 2,249 | 3,879 | +72% | 0 | 0 | — |
case-08 | pass→pass | 24,493 | 8,899 | -64% | 1 | 1 | 0% | 2,904 | 4,378 | +51% | 0 | 0 | — |
case-01 | pass→pass | 14,221 | 18,369 | +29% | 1 | 1 | 0% | 2,600 | 4,590 | +77% | 0 | 0 | — |
case-02 | pass→pass | 16,312 | 14,129 | -13% | 1 | 1 | 0% | 2,441 | 4,747 | +94% | 0 | 0 | — |
case-04 | pass→pass | 10,442 | 6,533 | -37% | 1 | 1 | 0% | 1,970 | 3,756 | +91% | 0 | 0 | — |
case-05 | fail→pass | 10,265 | 7,490 | -27% | 1 | 1 | 0% | 1,939 | 4,029 | +108% | 0 | 0 | — |
case-06 | pass→pass | 8,288 | 9,731 | +17% | 1 | 1 | 0% | 1,663 | 4,580 | +175% | 0 | 0 | — |
case-07 | pass→pass | 16,639 | 10,316 | -38% | 1 | 1 | 0% | 2,495 | 4,500 | +80% | 0 | 0 | — |
case-09 | pass→pass | 15,093 | 14,919 | -1% | 1 | 1 | 0% | 2,698 | 5,312 | +97% | 0 | 0 | — |
case-10 | fail→pass | 15,707 | 13,044 | -17% | 1 | 1 | 0% | 2,956 | 4,901 | +66% | 0 | 0 | — |
case-11 | pass→pass | 8,319 | 14,083 | +69% | 1 | 1 | 0% | 1,543 | 3,523 | +128% | 0 | 0 | — |
case-12 | pass→pass | 9,991 | 5,772 | -42% | 1 | 1 | 0% | 1,749 | 3,711 | +112% | 0 | 0 | — |
case-13 | pass→pass | 14,539 | 11,750 | -19% | 1 | 1 | 0% | 2,390 | 4,618 | +93% | 0 | 0 | — |
case-14 | pass→pass | 14,413 | 14,021 | -3% | 1 | 1 | 0% | 2,435 | 5,282 | +117% | 0 | 0 | — |
case-15 | pass→pass | 15,931 | 15,720 | -1% | 1 | 1 | 0% | 2,563 | 5,117 | +100% | 0 | 0 | — |
case-16 | fail→fail | 9,972 | 7,480 | -25% | 1 | 1 | 0% | 2,005 | 4,082 | +104% | 0 | 0 | — |
case-17 | pass→pass | 10,383 | 5,796 | -44% | 1 | 1 | 0% | 2,081 | 3,727 | +79% | 0 | 0 | — |
case-18 | pass→pass | 5,670 | 3,875 | -32% | 1 | 1 | 0% | 981 | 3,188 | +225% | 0 | 0 | — |
case-19 | pass→fail | 10,294 | 7,293 | -29% | 1 | 1 | 0% | 2,000 | 3,983 | +99% | 0 | 0 | — |
case-20 | pass→fail | 10,121 | 12,236 | +21% | 1 | 1 | 0% | 2,010 | 5,046 | +151% | 0 | 0 | — |
case-21 | pass→pass | 11,334 | 12,715 | +12% | 1 | 1 | 0% | 2,098 | 4,955 | +136% | 0 | 0 | — |
case-22 | pass→pass | 9,250 | 6,776 | -27% | 1 | 1 | 0% | 1,788 | 3,876 | +117% | 0 | 0 | — |
case-23 | pass→pass | 7,947 | 7,817 | -2% | 1 | 1 | 0% | 1,647 | 4,108 | +149% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 23 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.