.claude/skills/python-env/SKILL.md
Python environment variable management using .env files and python-dotenv for simple projects
npx skillsauth add awannaphasch2016/jousef-landing python-envInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Focus: Managing environment variables in Python projects using .env files
Source: Patterns extracted from Google Sheets automation project, ss-automation, and general Python best practices
Use python-env when:
DO NOT use for:
| Project Type | Configuration Approach | When to Use |
|--------------|----------------------|-------------|
| Simple | .env + python-dotenv | Solo/small team, no compliance needs |
| Complex | Doppler + doppler run | Team secrets, AWS, production |
Decision tree:
Does project have compliance requirements (SOC2, HIPAA, etc.)?
├── YES → Use Doppler/Vault (see CLAUDE.md Principle #13)
└── NO → Continue...
Does project have team-shared secrets?
├── YES → Use Doppler (shared secrets need single source)
└── NO → Use .env pattern (this skill)
Load environment variables once at application entry point, not scattered throughout code.
# ✅ GOOD: Single load point
# src/config.py
from dotenv import load_dotenv
import os
load_dotenv() # Load once
DATABASE_URL = os.environ['DATABASE_URL']
API_KEY = os.environ['API_KEY']
# ❌ BAD: Scattered loading
# src/database.py
from dotenv import load_dotenv
load_dotenv() # Loading again!
# src/api.py
from dotenv import load_dotenv
load_dotenv() # And again!
Validate ALL required environment variables at application startup, not when first used.
# src/config.py
from dotenv import load_dotenv
import os
load_dotenv()
REQUIRED_VARS = [
'DATABASE_URL',
'API_KEY',
'SECRET_KEY',
]
def validate_config():
"""Validate required configuration at startup."""
missing = [var for var in REQUIRED_VARS if not os.environ.get(var)]
if missing:
raise ValueError(
f"Missing required environment variables: {missing}\n"
f"Copy .env.example to .env and fill in values."
)
# Call at import time
validate_config()
# Export validated config
DATABASE_URL = os.environ['DATABASE_URL']
API_KEY = os.environ['API_KEY']
SECRET_KEY = os.environ['SECRET_KEY']
Use hierarchical .env files for different contexts:
project/
├── .env.example # Template (committed to git)
├── .env # Shared defaults (gitignored, optional)
├── .env.local # Personal overrides (gitignored, highest priority)
└── .env.test # Test environment (gitignored)
Priority order (highest to lowest):
.env.local - Personal settings (API keys, local paths).env - Shared defaults.env.example - Documentation only (committed)Loading pattern:
from dotenv import load_dotenv
# Load in reverse priority (last loaded wins with override=True)
load_dotenv('.env') # Base defaults
load_dotenv('.env.local', override=True) # Personal overrides
Always gitignore actual .env files:
# Environment variables
.env
.env.local
.env.*.local
.env.test
# Keep example (documentation)
!.env.example
Create .env.example as documentation and template:
# .env.example - Copy to .env and fill in values
# Database Configuration
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# API Keys (get from: https://example.com/api-keys)
API_KEY=your-api-key-here
SECRET_KEY=generate-with-openssl-rand-hex-32
# Optional Configuration
DEBUG=false
LOG_LEVEL=INFO
Best practices for .env.example:
pip install python-dotenv
# .env.example
DATABASE_URL=postgresql://localhost/mydb
API_KEY=your-api-key-here
DEBUG=false
"""
Application configuration.
Load environment variables from .env files and validate required configuration.
"""
from pathlib import Path
from dotenv import load_dotenv
import os
# Determine project root
PROJECT_ROOT = Path(__file__).parent.parent
# Load environment variables (order matters: later overrides earlier)
load_dotenv(PROJECT_ROOT / '.env')
load_dotenv(PROJECT_ROOT / '.env.local', override=True)
# Required variables (fail fast if missing)
REQUIRED = ['DATABASE_URL', 'API_KEY']
def _validate():
missing = [v for v in REQUIRED if not os.environ.get(v)]
if missing:
raise ValueError(
f"Missing required environment variables: {missing}\n"
f"Copy .env.example to .env and fill in values:\n"
f" cp .env.example .env"
)
_validate()
# Export configuration
DATABASE_URL = os.environ['DATABASE_URL']
API_KEY = os.environ['API_KEY']
DEBUG = os.environ.get('DEBUG', 'false').lower() == 'true'
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
# Environment variables
.env
.env.local
.env.*.local
!.env.example
cp .env.example .env
# Edit .env with your values
Convert string environment variables to proper types:
# src/config.py
import os
from dotenv import load_dotenv
load_dotenv()
# Boolean
DEBUG = os.environ.get('DEBUG', 'false').lower() in ('true', '1', 'yes')
# Integer
PORT = int(os.environ.get('PORT', '8000'))
TIMEOUT = int(os.environ.get('TIMEOUT', '30'))
# Float
RATE_LIMIT = float(os.environ.get('RATE_LIMIT', '1.0'))
# List (comma-separated)
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',')
# Optional (with explicit None)
OPTIONAL_API_KEY = os.environ.get('OPTIONAL_API_KEY') # None if not set
Organize related configuration into classes:
# src/config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class DatabaseConfig:
host: str = os.environ.get('DB_HOST', 'localhost')
port: int = int(os.environ.get('DB_PORT', '5432'))
user: str = os.environ.get('DB_USER', 'postgres')
password: str = os.environ.get('DB_PASSWORD', '')
database: str = os.environ.get('DB_NAME', 'mydb')
@property
def url(self) -> str:
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}"
@dataclass(frozen=True)
class APIConfig:
key: str = os.environ.get('API_KEY', '')
secret: str = os.environ.get('API_SECRET', '')
timeout: int = int(os.environ.get('API_TIMEOUT', '30'))
# Singleton instances
db = DatabaseConfig()
api = APIConfig()
# Usage: from config import db, api
# print(db.url)
# print(api.key)
Load different configurations based on environment:
# src/config.py
import os
from dotenv import load_dotenv
# Determine environment
ENV = os.environ.get('ENV', 'development')
# Load base config
load_dotenv('.env')
# Load environment-specific config
if ENV == 'production':
load_dotenv('.env.production', override=True)
elif ENV == 'test':
load_dotenv('.env.test', override=True)
else:
load_dotenv('.env.local', override=True)
# Always override with local (for development)
if ENV != 'production':
load_dotenv('.env.local', override=True)
For credentials stored in files (like Google Service Account):
# .env
GOOGLE_CREDENTIALS_PATH=credentials.json
SHEET_NAME=My Dashboard
# src/config.py
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
PROJECT_ROOT = Path(__file__).parent.parent
# Resolve path relative to project root
GOOGLE_CREDENTIALS_PATH = PROJECT_ROOT / os.environ.get(
'GOOGLE_CREDENTIALS_PATH',
'credentials.json'
)
def validate_files():
"""Validate required files exist."""
if not GOOGLE_CREDENTIALS_PATH.exists():
raise FileNotFoundError(
f"Credentials file not found: {GOOGLE_CREDENTIALS_PATH}\n"
f"See docs/GOOGLE_SETUP.md for setup instructions."
)
validate_files()
SHEET_NAME = os.environ.get('SHEET_NAME', 'Dashboard')
# ❌ BAD: Multiple load points cause confusion
# database.py
from dotenv import load_dotenv
load_dotenv()
# api.py
from dotenv import load_dotenv
load_dotenv() # Which .env file? What order?
# ✅ GOOD: Single config module
# database.py
from config import DATABASE_URL
# api.py
from config import API_KEY
# ❌ BAD: Silent fallback hides missing config
DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
# What if someone forgot to set DATABASE_URL? Uses wrong database!
# ✅ GOOD: Fail fast for required variables
DATABASE_URL = os.environ['DATABASE_URL'] # Raises KeyError if missing
# Or with explicit validation
if not os.environ.get('DATABASE_URL'):
raise ValueError("DATABASE_URL is required")
DATABASE_URL = os.environ['DATABASE_URL']
# ❌ BAD: Secrets in git history
git add .env
git commit -m "Add configuration"
# Now API keys are in git history forever!
# ✅ GOOD: Gitignore before first commit
echo ".env" >> .gitignore
git add .gitignore
git commit -m "Ignore .env files"
# ❌ BAD: No .env.example
# New developers: "What environment variables do I need?"
# ✅ GOOD: Self-documenting .env.example
# .env.example
# Database connection (required)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# API key from https://example.com/api (required)
API_KEY=your-key-here
# Debug mode (optional, default: false)
DEBUG=false
# conftest.py
import os
import pytest
from dotenv import load_dotenv
@pytest.fixture(scope='session', autouse=True)
def load_test_env():
"""Load test environment variables."""
# Load test-specific .env
load_dotenv('.env.test', override=True)
# Or set test values directly
os.environ['DATABASE_URL'] = 'sqlite:///:memory:'
os.environ['API_KEY'] = 'test-key'
# test_config.py
import os
import pytest
def test_config_validates_required_vars(monkeypatch):
"""Test that missing required vars raise error."""
# Remove required variable
monkeypatch.delenv('API_KEY', raising=False)
# Should raise on import
with pytest.raises(ValueError, match="Missing required"):
import importlib
import config
importlib.reload(config)
def test_config_uses_defaults(monkeypatch):
"""Test that optional vars have defaults."""
monkeypatch.delenv('DEBUG', raising=False)
from config import DEBUG
assert DEBUG == False # Default value
.claude/skills/python-env/
├── SKILL.md # This file (entry point)
├── PATTERNS.md # Additional patterns (if needed)
└── examples/
├── simple_config.py # Basic configuration example
└── typed_config.py # Type-safe configuration class
This skill complements CLAUDE.md Principle #13 (Secret Management Discipline):
| Scenario | Use This Skill | Use Doppler (Principle #13) |
|----------|---------------|---------------------------|
| Simple project | ✅ | ❌ |
| Team secrets | ❌ | ✅ |
| Production | ❌ | ✅ |
| Local development | ✅ | ✅ (via doppler run) |
| Open-source | ✅ | ❌ (contributors can't access) |
Principle overlap:
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
testing
Write comprehensive tests following project conventions (tiers, patterns, anti-patterns). Use when writing tests, improving test coverage, fixing failing tests, or reviewing test quality.
content-media
Clone and customize existing templates (landing pages, dashboards, admin panels) with style extraction, config-driven content, and theme customization
development
Create high-converting B2B landing pages using psychological section sequencing. Use when building landing pages for services, agencies, consultants, or B2B products. Provides 14-section framework optimized for conversion psychology.