Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Environment configuration and secrets management skill using UV for Python projects. Handles .env files, environment variables, secrets encryption, multi-environment setups, and secure configuration patterns. Use when setting up project environments, managing API keys, or implementing configuration best practices.
.claude/skills/aiskillstore-env-config/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 137% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 83% | 0% |
Expert environment configuration management for Python/FastAPI projects with secure secrets handling and multi-environment support.
| Pattern | Usage | |---------|-------| | Load .env | load_dotenv() at application start | | Access var | settings.DB_URL, settings.JWT_SECRET | | Required var | Field(..., description="Database URL") | | Optional var | DB_HOST: str = "localhost" | | Secret type | SecretStr for sensitive values |
project/
├── .env # Local development (NOT committed)
├── .env.example # Template with all required vars (committed)
├── .env.staging # Staging environment
├── .env.production # Production environment (managed by infra)
└── config/
├── __init__.py
└── settings.py # Pydantic BaseSettingspython# config/settings.py from functools import lru_cache from pydantic import Field, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", ) # Application APP_NAME: str = "ERP System" DEBUG: bool = False API_V1_PREFIX: str = "/v1" # Database DB_URL: str = Field( ..., description="PostgreSQL connection URL", examples=["postgresql://user:pass@localhost:5432/dbname"], ) DB_POOL_SIZE: int = Field(default=5, ge=1, le=100) # JWT Authentication JWT_SECRET_KEY: SecretStr = Field( ..., description="Secret key for JWT signing", ) JWT_ALGORITHM: str = "HS256" JWT_EXPIRATION_MINUTES: int = Field(default=15, ge=1) # Redis (Optional) REDIS_URL: str | None = None # Logging LOG_LEVEL: str = Field(default="INFO", pattern="^(DEBUG|INFO|WARNING|ERROR)$") # CORS CORS_ORIGINS: list[str] = ["http://localhost:3000"] @property def is_production(self) -> bool: return not self.DEBUG @lru_cache def get_settings() -> Settings: """Cached settings instance for application lifecycle.""" return Settings()
bash# .env.example - Copy to .env and fill in values # DO NOT commit actual secrets! # Application APP_NAME="ERP System" DEBUG=false API_V1_PREFIX="/v1" # Database (required) DB_URL="postgresql://user:password@localhost:5432/erp_db" # JWT Authentication (required - generate with: openssl rand -hex 32) JWT_SECRET_KEY="your-secret-key-here-generate-with-openssl-rand-hex-32" JWT_ALGORITHM="HS256" JWT_EXPIRATION_MINUTES=15 # Redis (optional) # REDIS_URL="redis://localhost:6379/0" # Logging LOG_LEVEL="INFO" # CORS CORS_ORIGINS="http://localhost:3000"
bash# .env - Local development only # NEVER commit this file to version control APP_NAME="ERP System" DEBUG=true API_V1_PREFIX="/v1" # Local PostgreSQL DB_URL="postgresql://postgres:postgres@localhost:5432/erp_dev" # Generate with: openssl rand -hex 32 JWT_SECRET_KEY="local-dev-secret-key-change-in-production" JWT_ALGORITHM="HS256" JWT_EXPIRATION_MINUTES=15 # Local Redis (if using) REDIS_URL="redis://localhost:6379/0" LOG_LEVEL="DEBUG" CORS_ORIGINS="http://localhost:3000,http://localhost:5173"
python# main.py from contextlib import asynccontextmanager from dotenv import load_dotenv load_dotenv() # Load .env file from config.settings import get_settings @asynccontextmanager async def lifespan(app): settings = get_settings() print(f"Starting {settings.APP_NAME} in {'DEBUG' if settings.DEBUG else 'PROD'} mode") yield print("Shutting down...") app = FastAPI( title=get_settings().APP_NAME, lifespan=lifespan, ) # Include routers from app.routers import fees, students app.include_router(fees.router, prefix=get_settings().API_V1_PREFIX) app.include_router(students.router, prefix=get_settings().API_V1_PREFIX)
python# database.py from sqlmodel import create_engine, Session from config.settings import get_settings settings = get_settings() engine = create_engine( settings.DB_URL.get_secret_value() if hasattr(settings.DB_URL, 'get_secret_value') else settings.DB_URL, pool_size=settings.DB_POOL_SIZE, max_overflow=10, ) def get_session(): with Session(engine) as session: yield session
python# auth/jwt.py from datetime import timedelta from config.settings import get_settings settings = get_settings() JWT_SECRET = settings.JWT_SECRET_KEY.get_secret_value() JWT_ALGORITHM = settings.JWT_ALGORITHM ACCESS_TOKEN_EXPIRE_MINUTES = settings.JWT_EXPIRATION_MINUTES def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str: # ... token creation logic pass
python# config/settings.py class Settings(BaseSettings): # ... shared settings @classmethod def from_env(cls, env: str = "development") -> "Settings": """Load settings for specific environment.""" env_file = { "development": ".env", "staging": ".env.staging", "production": ".env.production", }.get(env, ".env") return cls(_env_file=env_file)
bash# Production should use environment variables, not .env files # Set these in your deployment platform (Docker, K8s, Cloud Run, etc.) export DB_URL="postgresql://prod_user:prod_pass@prod-db.example.com:5432/erp_prod" export JWT_SECRET_KEY="production-secret-key-from-secrets-manager" export DEBUG=false export LOG_LEVEL="WARNING"
bash# Generate secure random secret openssl rand -hex 32 # For JWT_SECRET_KEY # Generate database password openssl rand -base64 32
python# scripts/rotate_secret.py """Rotate a secret in all environments.""" import os import re def rotate_secret(env_file: str, key: str, new_value: str): """Replace secret value in .env file.""" with open(env_file, "r") as f: content = f.read() # Pattern to match KEY=value pattern = f"^{key}=.*$" replacement = f"{key}={new_value}" new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) with open(env_file, "w") as f: f.write(new_content) print(f"Rotated {key} in {env_file}") if __name__ == "__main__": import sys if len(sys.argv) != 4: print("Usage: rotate_secret.py <env_file> <key> <new_value>") sys.exit(1) rotate_secret(sys.argv[1], sys.argv[2], sys.argv[3])
Field(..., ...) validationSecretStr for sensitive values, never print settingsopenssl rand -hex 32| Skill | Integration Point | |-------|-------------------| | @jwt-auth | JWT_SECRET_KEY from settings | | @sqlmodel-crud | DB_URL from settings | | @fastapi-app | All app settings from settings | | @db-migration | Database URL for migrations | | @api-route-design | API prefix, CORS origins |
SecretStr for passwords, API keys, tokens.env files to version controlpython# config/validate.py """Validate required configuration at startup.""" from pydantic import ValidationError from config.settings import Settings def validate_settings() -> bool: """Ensure all required settings are configured.""" try: settings = Settings() return True except ValidationError as e: print("Configuration validation failed:") for error in e.errors(): print(f" - {error['loc'][0]}: {error['msg']}") return False if __name__ == "__main__": if not validate_settings(): exit(1)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 8,335 | 10,703 | +28% | 1 | 1 | 0% | 2,030 | 4,727 | +133% | 0 | 0 | — |
case-01 | fail→pass | 20,941 | 18,329 | -12% | 1 | 1 | 0% | 3,159 | 5,197 | +65% | 0 | 0 | — |
case-02 | fail→fail | 20,891 | 21,627 | +4% | 1 | 1 | 0% | 3,169 | 5,968 | +88% | 0 | 0 | — |
case-03 | fail→fail | 27,560 | 21,510 | -22% | 1 | 1 | 0% | 4,256 | 6,207 | +46% | 0 | 0 | — |
case-05 | pass→pass | 12,184 | 13,663 | +12% | 1 | 1 | 0% | 1,427 | 4,217 | +196% | 0 | 0 | — |
case-06 | pass→pass | 4,075 | 10,123 | +148% | 1 | 1 | 0% | 776 | 3,487 | +349% | 0 | 0 | — |
case-07 | pass→pass | 10,676 | 17,760 | +66% | 1 | 1 | 0% | 2,001 | 4,554 | +128% | 0 | 0 | — |
case-08 | fail→fail | 26,793 | 9,358 | -65% | 1 | 1 | 0% | 1,528 | 4,304 | +182% | 0 | 0 | — |
case-09 | fail→pass | 18,847 | 16,182 | -14% | 1 | 1 | 0% | 2,630 | 4,704 | +79% | 0 | 0 | — |
case-10 | fail→fail | 28,071 | 13,824 | -51% | 1 | 1 | 0% | 1,796 | 4,149 | +131% | 0 | 0 | — |
case-11 | pass→pass | 15,140 | 15,789 | +4% | 1 | 1 | 0% | 2,071 | 4,334 | +109% | 0 | 0 | — |
case-12 | pass→pass | 15,690 | 18,220 | +16% | 1 | 1 | 0% | 2,991 | 5,007 | +67% | 0 | 0 | — |
case-13 | pass→pass | 6,373 | 5,105 | -20% | 1 | 1 | 0% | 1,237 | 3,483 | +182% | 0 | 0 | — |
case-18 | pass→pass | 14,919 | 10,141 | -32% | 1 | 1 | 0% | 1,931 | 3,354 | +74% | 0 | 0 | — |
case-14 | pass→pass | 11,168 | 9,024 | -19% | 1 | 1 | 0% | 1,189 | 3,234 | +172% | 0 | 0 | — |
case-15 | pass→pass | 9,427 | 15,059 | +60% | 1 | 1 | 0% | 1,707 | 3,718 | +118% | 0 | 0 | — |
case-16 | pass→pass | 17,388 | 9,622 | -45% | 1 | 1 | 0% | 3,116 | 4,490 | +44% | 0 | 0 | — |
case-17 | pass→pass | 6,526 | 10,083 | +55% | 1 | 1 | 0% | 1,369 | 3,501 | +156% | 0 | 0 | — |
case-19 | pass→pass | 13,930 | 15,813 | +14% | 1 | 1 | 0% | 2,523 | 5,529 | +119% | 0 | 0 | — |
case-20 | fail→pass | 11,054 | 11,863 | +7% | 1 | 1 | 0% | 1,959 | 4,645 | +137% | 0 | 0 | — |
case-21 | fail→pass | 12,693 | 9,470 | -25% | 1 | 1 | 0% | 1,402 | 3,368 | +140% | 0 | 0 | — |
case-22 | pass→pass | 26,155 | 6,729 | -74% | 1 | 1 | 0% | 2,037 | 3,579 | +76% | 0 | 0 | — |
case-23 | fail→pass | 11,566 | 15,609 | +35% | 1 | 1 | 0% | 2,534 | 4,637 | +83% | 0 | 0 | — |
case-24 | pass→pass | 16,866 | 6,878 | -59% | 1 | 1 | 0% | 2,073 | 3,732 | +80% | 0 | 0 | — |
case-25 | pass→pass | 12,488 | 8,970 | -28% | 1 | 1 | 0% | 1,458 | 3,269 | +124% | 0 | 0 | — |
case-26 | fail→pass | 8,423 | 4,882 | -42% | 1 | 1 | 0% | 1,459 | 3,397 | +133% | 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. 26 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 26 comparable cases.
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.