Comprehensive guide for managing Az Core framework configuration across different environments.
Overview
Configuration management is critical for maintaining consistency, security, and reliability across development, staging, and production environments. This guide covers configuration strategies, tools, best practices, and security considerations.
Configuration Strategies
1. Hierarchical Configuration
Layer configurations from most general to most specific.
# config/base.py
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
@dataclass
class BaseConfig:
"""Base configuration shared across all environments."""
# Application
app_name: str = "arc"
app_version: str = "1.0.0"
debug: bool = False
# Logging
log_level: str = "INFO"
log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# LLM Defaults
llm_model: str = "gpt-4o-mini"
llm_temperature: float = 0.5
llm_max_tokens: int = 4096
llm_timeout: int = 60
# Caching
cache_enabled: bool = True
cache_ttl: int = 3600
cache_max_size: int = 1000
# Rate Limiting
rate_limit_enabled: bool = True
rate_limit_requests: int = 100
rate_limit_window: int = 60
def to_dict(self) -> Dict[str, Any]:
"""Convert config to dictionary."""
return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
# config/development.py
@dataclass
class DevelopmentConfig(BaseConfig):
"""Development environment configuration."""
debug: bool = True
log_level: str = "DEBUG"
# Use cheaper models in development
llm_model: str = "gpt-4o-mini"
llm_temperature: float = 0.7 # More creative
# Relaxed rate limits
rate_limit_requests: int = 1000
# Development-specific
hot_reload: bool = True
cors_origins: list = field(default_factory=lambda: ["http://localhost:3000"])
# config/production.py
@dataclass
class ProductionConfig(BaseConfig):
"""Production environment configuration."""
debug: bool = False
log_level: str = "WARNING"
# Production models
llm_model: str = "gpt-4o"
llm_temperature: float = 0.3 # More deterministic
# Strict rate limits
rate_limit_requests: int = 50
# Production-specific
workers: int = 4
cors_origins: list = field(default_factory=lambda: ["https://app.example.com"])
sentry_enabled: bool = True
# config/staging.py
@dataclass
class StagingConfig(ProductionConfig):
"""Staging environment configuration (mirrors production)."""
log_level: str = "INFO" # More verbose for testing
# Slightly relaxed rate limits
rate_limit_requests: int = 100
# config/__init__.py
import os
from typing import Union
def get_config() -> Union[DevelopmentConfig, StagingConfig, ProductionConfig]:
"""Get configuration based on environment."""
env = os.getenv("APP_ENV", "development").lower()
config_map = {
"development": DevelopmentConfig,
"staging": StagingConfig,
"production": ProductionConfig
}
config_class = config_map.get(env, DevelopmentConfig)
return config_class()
# Usage
config = get_config()
2. Feature Flags
Control features without code changes.
# config/features.py
from dataclasses import dataclass
from typing import Dict, Optional
import os
@dataclass
class FeatureFlags:
"""Feature flags for gradual rollout."""
# Core features
enable_rl_learning: bool = False
enable_semantic_cache: bool = True
enable_streaming: bool = True
# Experimental features
enable_multi_agent: bool = False
enable_tool_discovery: bool = False
enable_auto_optimization: bool = False
# Beta features
enable_vision: bool = False
enable_voice: bool = False
@classmethod
def from_env(cls) -> 'FeatureFlags':
"""Load feature flags from environment variables."""
return cls(
enable_rl_learning=os.getenv("FEATURE_RL_LEARNING", "false").lower() == "true",
enable_semantic_cache=os.getenv("FEATURE_SEMANTIC_CACHE", "true").lower() == "true",
enable_streaming=os.getenv("FEATURE_STREAMING", "true").lower() == "true",
enable_multi_agent=os.getenv("FEATURE_MULTI_AGENT", "false").lower() == "true",
enable_tool_discovery=os.getenv("FEATURE_TOOL_DISCOVERY", "false").lower() == "true",
enable_auto_optimization=os.getenv("FEATURE_AUTO_OPTIMIZATION", "false").lower() == "true",
enable_vision=os.getenv("FEATURE_VISION", "false").lower() == "true",
enable_voice=os.getenv("FEATURE_VOICE", "false").lower() == "true",
)
def is_enabled(self, feature: str) -> bool:
"""Check if feature is enabled."""
return getattr(self, f"enable_{feature}", False)
# Usage
features = FeatureFlags.from_env()
if features.enable_rl_learning:
# Use RL-enhanced agent
agent = RLAgent(...)
else:
# Use standard agent
agent = ReactAgent(...)
Environment-Specific Configuration
Directory Structure
config/
├── __init__.py # Config loader
├── base.py # Base configuration
├── development.py # Development config
├── staging.py # Staging config
├── production.py # Production config
├── testing.py # Testing config
├── features.py # Feature flags
└── validation.py # Config validation
.env # Local development (not committed)
.env.example # Example env file (committed)
.env.staging # Staging environment
.env.production # Production environment (secure storage)
Environment Detection
# config/environment.py
import os
from enum import Enum
class Environment(str, Enum):
"""Environment types."""
DEVELOPMENT = "development"
STAGING = "staging"
PRODUCTION = "production"
TESTING = "testing"
def get_environment() -> Environment:
"""Detect current environment."""
env = os.getenv("APP_ENV", "development").lower()
# Automatic detection fallbacks
if env not in [e.value for e in Environment]:
# Check for common environment indicators
if os.getenv("CI"):
return Environment.TESTING
elif os.getenv("KUBERNETES_SERVICE_HOST"):
# Running in Kubernetes - assume production
return Environment.PRODUCTION
elif os.getenv("AWS_EXECUTION_ENV"):
# Running in AWS Lambda
return Environment.PRODUCTION
else:
return Environment.DEVELOPMENT
return Environment(env)
def is_production() -> bool:
"""Check if running in production."""
return get_environment() == Environment.PRODUCTION
def is_development() -> bool:
"""Check if running in development."""
return get_environment() == Environment.DEVELOPMENT
Configuration Files
YAML Configuration
# config/config.yaml
common: &common
app:
name: arc
version: 1.0.0
logging:
level: INFO
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
llm:
model: gpt-4o-mini
temperature: 0.5
max_tokens: 4096
timeout: 60
cache:
enabled: true
ttl: 3600
max_size: 1000
development:
<<: *common
app:
debug: true
logging:
level: DEBUG
llm:
model: gpt-4o-mini
temperature: 0.7
staging:
<<: *common
logging:
level: INFO
llm:
model: gpt-4o
production:
<<: *common
app:
debug: false
logging:
level: WARNING
llm:
model: gpt-4o
temperature: 0.3
Load YAML Config
# config/loader.py
import yaml
from pathlib import Path
from typing import Dict, Any
def load_yaml_config(config_path: str = "config/config.yaml") -> Dict[str, Any]:
"""Load configuration from YAML file."""
config_file = Path(config_path)
if not config_file.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
with open(config_file, 'r') as f:
config = yaml.safe_load(f)
# Get environment-specific config
env = os.getenv("APP_ENV", "development")
return config.get(env, config.get("common", {}))
# config/settings.py
from pydantic import BaseSettings, Field
from typing import Optional
class Settings(BaseSettings):
"""Settings loaded from YAML and environment variables."""
# Application
app_name: str = Field(default="arc")
app_version: str = Field(default="1.0.0")
app_debug: bool = Field(default=False)
# Logging
log_level: str = Field(default="INFO")
# LLM
llm_model: str = Field(default="gpt-4o-mini")
llm_temperature: float = Field(default=0.5)
llm_max_tokens: int = Field(default=4096)
# Secrets (from environment only)
openai_api_key: str = Field(..., env="OPENAI_API_KEY")
anthropic_api_key: Optional[str] = Field(None, env="ANTHROPIC_API_KEY")
class Config:
env_file = ".env"
case_sensitive = False
@classmethod
def from_yaml(cls, yaml_path: str = "config/config.yaml") -> 'Settings':
"""Load settings from YAML and environment."""
yaml_config = load_yaml_config(yaml_path)
# Flatten nested config
flat_config = {}
for section, values in yaml_config.items():
if isinstance(values, dict):
for key, value in values.items():
flat_config[f"{section}_{key}"] = value
# Create settings (env vars override YAML)
return cls(**flat_config)
TOML Configuration
# config/config.toml
[app]
name = "arc"
version = "1.0.0"
debug = false
[logging]
level = "INFO"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
[llm]
model = "gpt-4o-mini"
temperature = 0.5
max_tokens = 4096
timeout = 60
[cache]
enabled = true
ttl = 3600
max_size = 1000
[rate_limit]
enabled = true
requests = 100
window = 60
[development]
debug = true
log_level = "DEBUG"
[production]
debug = false
log_level = "WARNING"
import tomli
from pathlib import Path
def load_toml_config(config_path: str = "config/config.toml") -> dict:
"""Load configuration from TOML file."""
with open(config_path, 'rb') as f:
config = tomli.load(f)
return config
Environment Variables
.env File Structure
# .env.example - Commit this to version control
# Copy to .env and fill in actual values
# Environment
APP_ENV=development
APP_DEBUG=true
# Server
HOST=0.0.0.0
PORT=8000
WORKERS=4
# LLM Configuration
OPENAI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
LLM_MODEL=gpt-4o-mini
LLM_TEMPERATURE=0.5
LLM_MAX_TOKENS=4096
LLM_TIMEOUT=60
# Caching
CACHE_ENABLED=true
CACHE_TTL=3600
CACHE_MAX_SIZE=1000
# Rate Limiting
RATE_LIMIT_ENABLED=true
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=60
# Database (if using)
DATABASE_URL=postgresql://user:pass@localhost:5432/arc
# Monitoring
SENTRY_DSN=
DATADOG_API_KEY=
PROMETHEUS_PORT=9090
# Feature Flags
FEATURE_RL_LEARNING=false
FEATURE_SEMANTIC_CACHE=true
FEATURE_STREAMING=true
# Security
CORS_ORIGINS=http://localhost:3000
API_KEY_REQUIRED=false
JWT_SECRET=
Loading Environment Variables
# config/env.py
from dotenv import load_dotenv
import os
from pathlib import Path
from typing import Optional, Any
def load_environment(env_file: Optional[str] = None) -> None:
"""Load environment variables from file."""
if env_file is None:
# Auto-detect based on APP_ENV
env = os.getenv("APP_ENV", "development")
env_file = f".env.{env}"
# Fallback to .env if specific file doesn't exist
if not Path(env_file).exists():
env_file = ".env"
if Path(env_file).exists():
load_dotenv(env_file, override=True)
print(f"Loaded environment from {env_file}")
else:
print(f"Warning: Environment file {env_file} not found")
def get_env(key: str, default: Any = None, required: bool = False) -> Optional[str]:
"""Get environment variable with validation."""
value = os.getenv(key, default)
if required and value is None:
raise ValueError(f"Required environment variable {key} is not set")
return value
def get_env_bool(key: str, default: bool = False) -> bool:
"""Get boolean environment variable."""
value = os.getenv(key, str(default))
return value.lower() in ('true', '1', 'yes', 'on')
def get_env_int(key: str, default: int = 0) -> int:
"""Get integer environment variable."""
value = os.getenv(key, str(default))
try:
return int(value)
except ValueError:
return default
def get_env_float(key: str, default: float = 0.0) -> float:
"""Get float environment variable."""
value = os.getenv(key, str(default))
try:
return float(value)
except ValueError:
return default
def get_env_list(key: str, default: list = None, separator: str = ",") -> list:
"""Get list environment variable."""
if default is None:
default = []
value = os.getenv(key)
if not value:
return default
return [item.strip() for item in value.split(separator)]
# Usage
load_environment()
OPENAI_API_KEY = get_env("OPENAI_API_KEY", required=True)
DEBUG = get_env_bool("APP_DEBUG", default=False)
PORT = get_env_int("PORT", default=8000)
TEMPERATURE = get_env_float("LLM_TEMPERATURE", default=0.5)
CORS_ORIGINS = get_env_list("CORS_ORIGINS", default=["*"])
Secrets Management
1. AWS Secrets Manager
# config/secrets_aws.py
import boto3
import json
from typing import Dict, Any
class AWSSecretsManager:
"""Manage secrets using AWS Secrets Manager."""
def __init__(self, region_name: str = "us-east-1"):
self.client = boto3.client('secretsmanager', region_name=region_name)
def get_secret(self, secret_name: str) -> Dict[str, Any]:
"""Retrieve secret from AWS Secrets Manager."""
try:
response = self.client.get_secret_value(SecretId=secret_name)
if 'SecretString' in response:
return json.loads(response['SecretString'])
else:
# Binary secret
return response['SecretBinary']
except Exception as e:
print(f"Error retrieving secret {secret_name}: {e}")
raise
def load_secrets_to_env(self, secret_name: str) -> None:
"""Load secrets into environment variables."""
secrets = self.get_secret(secret_name)
for key, value in secrets.items():
os.environ[key] = str(value)
# Usage
secrets_manager = AWSSecretsManager()
secrets_manager.load_secrets_to_env("arc/production")
2. HashiCorp Vault
# config/secrets_vault.py
import hvac
from typing import Dict, Any
class VaultSecretsManager:
"""Manage secrets using HashiCorp Vault."""
def __init__(
self,
url: str = "http://127.0.0.1:8200",
token: str = None,
mount_point: str = "secret"
):
self.client = hvac.Client(url=url, token=token)
self.mount_point = mount_point
if not self.client.is_authenticated():
raise ValueError("Vault authentication failed")
def get_secret(self, path: str) -> Dict[str, Any]:
"""Retrieve secret from Vault."""
try:
response = self.client.secrets.kv.v2.read_secret_version(
path=path,
mount_point=self.mount_point
)
return response['data']['data']
except Exception as e:
print(f"Error retrieving secret {path}: {e}")
raise
def set_secret(self, path: str, secret: Dict[str, Any]) -> None:
"""Store secret in Vault."""
try:
self.client.secrets.kv.v2.create_or_update_secret(
path=path,
secret=secret,
mount_point=self.mount_point
)
except Exception as e:
print(f"Error storing secret {path}: {e}")
raise
def load_secrets_to_env(self, path: str) -> None:
"""Load secrets into environment variables."""
secrets = self.get_secret(path)
for key, value in secrets.items():
os.environ[key] = str(value)
# Usage
vault = VaultSecretsManager(
url="https://vault.example.com",
token=os.getenv("VAULT_TOKEN")
)
vault.load_secrets_to_env("arc/production")
3. Google Secret Manager
# config/secrets_gcp.py
from google.cloud import secretmanager
from typing import Dict, Any
import json
class GCPSecretsManager:
"""Manage secrets using Google Secret Manager."""
def __init__(self, project_id: str):
self.client = secretmanager.SecretManagerServiceClient()
self.project_id = project_id
def get_secret(self, secret_id: str, version: str = "latest") -> str:
"""Retrieve secret from Google Secret Manager."""
try:
name = f"projects/{self.project_id}/secrets/{secret_id}/versions/{version}"
response = self.client.access_secret_version(request={"name": name})
return response.payload.data.decode('UTF-8')
except Exception as e:
print(f"Error retrieving secret {secret_id}: {e}")
raise
def create_secret(self, secret_id: str, data: str) -> None:
"""Create new secret in Google Secret Manager."""
parent = f"projects/{self.project_id}"
# Create secret
self.client.create_secret(
request={
"parent": parent,
"secret_id": secret_id,
"secret": {"replication": {"automatic": {}}},
}
)
# Add secret version
parent = f"projects/{self.project_id}/secrets/{secret_id}"
payload = data.encode('UTF-8')
self.client.add_secret_version(
request={"parent": parent, "payload": {"data": payload}}
)
def load_secrets_to_env(self, secret_mappings: Dict[str, str]) -> None:
"""Load secrets into environment variables."""
for env_var, secret_id in secret_mappings.items():
value = self.get_secret(secret_id)
os.environ[env_var] = value
# Usage
gcp_secrets = GCPSecretsManager(project_id="my-project")
gcp_secrets.load_secrets_to_env({
"OPENAI_API_KEY": "openai-api-key",
"DATABASE_URL": "database-url"
})
4. Kubernetes Secrets
# k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: arc-secrets
namespace: production
type: Opaque
stringData:
OPENAI_API_KEY: "sk-xxx"
ANTHROPIC_API_KEY: "sk-ant-xxx"
DATABASE_URL: "postgresql://user:pass@host:5432/arc"
SENTRY_DSN: "https://xxx@sentry.io/xxx"
# k8s/deployment.yaml (excerpt)
spec:
containers:
- name: arc
envFrom:
- secretRef:
name: arc-secrets
Configuration Validation
Pydantic Validation
# config/validated_settings.py
from pydantic import BaseSettings, Field, validator, root_validator
from typing import Optional, List
import re
class ValidatedSettings(BaseSettings):
"""Settings with comprehensive validation."""
# Application
app_name: str = Field(..., min_length=1, max_length=100)
app_env: str = Field(..., regex="^(development|staging|production)$")
# Server
host: str = Field("0.0.0.0")
port: int = Field(8000, ge=1, le=65535)
workers: int = Field(4, ge=1, le=32)
# LLM
openai_api_key: str = Field(..., min_length=20)
llm_model: str = Field("gpt-4o-mini")
llm_temperature: float = Field(0.5, ge=0.0, le=2.0)
llm_max_tokens: int = Field(4096, ge=1, le=128000)
llm_timeout: int = Field(60, ge=1, le=300)
# Rate Limiting
rate_limit_requests: int = Field(100, ge=1)
rate_limit_window: int = Field(60, ge=1)
# URLs
cors_origins: List[str] = Field(default_factory=list)
database_url: Optional[str] = Field(None)
@validator('openai_api_key')
def validate_openai_key(cls, v):
"""Validate OpenAI API key format."""
if not v.startswith('sk-'):
raise ValueError('OpenAI API key must start with sk-')
return v
@validator('database_url')
def validate_database_url(cls, v):
"""Validate database URL format."""
if v is None:
return v
pattern = r'^postgresql://[\w\-]+:[\w\-]+@[\w\.\-]+:\d+/[\w\-]+$'
if not re.match(pattern, v):
raise ValueError('Invalid PostgreSQL URL format')
return v
@validator('cors_origins')
def validate_cors_origins(cls, v):
"""Validate CORS origins."""
for origin in v:
if origin != '*' and not origin.startswith(('http://', 'https://')):
raise ValueError(f'Invalid CORS origin: {origin}')
return v
@root_validator
def validate_production_config(cls, values):
"""Validate production-specific requirements."""
if values.get('app_env') == 'production':
# Production must have specific settings
if values.get('app_debug', False):
raise ValueError('Debug must be disabled in production')
if '*' in values.get('cors_origins', []):
raise ValueError('Wildcard CORS not allowed in production')
if not values.get('database_url'):
raise ValueError('Database URL required in production')
return values
class Config:
env_file = ".env"
case_sensitive = False
# Usage
try:
settings = ValidatedSettings()
print("Configuration valid")
except ValueError as e:
print(f"Configuration error: {e}")
raise
Dynamic Configuration
Remote Configuration Service
# config/remote_config.py
import requests
import time
from threading import Thread
from typing import Dict, Any, Callable
class RemoteConfigManager:
"""Manage configuration from remote service."""
def __init__(
self,
config_url: str,
api_key: str,
refresh_interval: int = 300
):
self.config_url = config_url
self.api_key = api_key
self.refresh_interval = refresh_interval
self.config: Dict[str, Any] = {}
self.callbacks: List[Callable] = []
# Initial fetch
self.refresh_config()
# Start background refresh
self._start_refresh_thread()
def refresh_config(self) -> None:
"""Fetch latest configuration from remote service."""
try:
response = requests.get(
self.config_url,
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=10
)
response.raise_for_status()
new_config = response.json()
# Check if config changed
if new_config != self.config:
old_config = self.config.copy()
self.config = new_config
# Notify callbacks
for callback in self.callbacks:
callback(old_config, new_config)
print("Configuration updated from remote service")
except Exception as e:
print(f"Error fetching remote configuration: {e}")
def get(self, key: str, default: Any = None) -> Any:
"""Get configuration value."""
return self.config.get(key, default)
def on_change(self, callback: Callable) -> None:
"""Register callback for configuration changes."""
self.callbacks.append(callback)
def _start_refresh_thread(self) -> None:
"""Start background thread to refresh configuration."""
def refresh_loop():
while True:
time.sleep(self.refresh_interval)
self.refresh_config()
thread = Thread(target=refresh_loop, daemon=True)
thread.start()
# Usage
remote_config = RemoteConfigManager(
config_url="https://config.example.com/api/config",
api_key=os.getenv("CONFIG_API_KEY"),
refresh_interval=300 # 5 minutes
)
# Register change handler
def on_config_change(old_config, new_config):
print(f"Config changed: {old_config} -> {new_config}")
# Reload components as needed
remote_config.on_change(on_config_change)
# Get config value
llm_model = remote_config.get("llm_model", "gpt-4o-mini")
Configuration Versioning
Version Control Strategy
# .gitignore
.env
.env.local
.env.*.local
secrets/
*.key
*.pem
# Commit these
.env.example
config/*.py
config/*.yaml
config/*.toml
Configuration History
# config/versioning.py
import json
import hashlib
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List
class ConfigVersioning:
"""Track configuration changes over time."""
def __init__(self, history_dir: str = "config_history"):
self.history_dir = Path(history_dir)
self.history_dir.mkdir(exist_ok=True)
def save_version(self, config: Dict[str, Any], description: str = "") -> str:
"""Save configuration version."""
timestamp = datetime.now().isoformat()
config_hash = hashlib.sha256(
json.dumps(config, sort_keys=True).encode()
).hexdigest()[:8]
version_file = self.history_dir / f"{timestamp}_{config_hash}.json"
version_data = {
"timestamp": timestamp,
"hash": config_hash,
"description": description,
"config": config
}
with open(version_file, 'w') as f:
json.dump(version_data, f, indent=2)
return config_hash
def get_version(self, config_hash: str) -> Dict[str, Any]:
"""Retrieve specific configuration version."""
for version_file in self.history_dir.glob(f"*_{config_hash}.json"):
with open(version_file, 'r') as f:
return json.load(f)
raise ValueError(f"Version {config_hash} not found")
def list_versions(self) -> List[Dict[str, str]]:
"""List all configuration versions."""
versions = []
for version_file in sorted(self.history_dir.glob("*.json"), reverse=True):
with open(version_file, 'r') as f:
data = json.load(f)
versions.append({
"timestamp": data["timestamp"],
"hash": data["hash"],
"description": data.get("description", "")
})
return versions
def rollback(self, config_hash: str) -> Dict[str, Any]:
"""Rollback to previous configuration version."""
version_data = self.get_version(config_hash)
return version_data["config"]
# Usage
versioning = ConfigVersioning()
# Save current config
current_config = config.to_dict()
version_hash = versioning.save_version(
current_config,
description="Production deployment v2.0.0"
)
# List versions
versions = versioning.list_versions()
for v in versions:
print(f"{v['timestamp']}: {v['hash']} - {v['description']}")
# Rollback if needed
previous_config = versioning.rollback(version_hash)
Configuration Tools
Configuration Inspector
# tools/config_inspector.py
from typing import Dict, Any
import json
class ConfigInspector:
"""Inspect and validate configuration."""
def __init__(self, config: Dict[str, Any]):
self.config = config
def print_summary(self) -> None:
"""Print configuration summary."""
print("Configuration Summary")
print("=" * 50)
print(json.dumps(self.config, indent=2, default=str))
def check_required_keys(self, required_keys: List[str]) -> bool:
"""Check if all required keys are present."""
missing = [key for key in required_keys if key not in self.config]
if missing:
print(f"Missing required keys: {missing}")
return False
return True
def check_sensitive_data(self) -> List[str]:
"""Check for potentially exposed sensitive data."""
sensitive_patterns = [
'password', 'secret', 'key', 'token', 'credential'
]
exposed = []
for key, value in self.config.items():
if any(pattern in key.lower() for pattern in sensitive_patterns):
if value and len(str(value)) > 0:
exposed.append(key)
if exposed:
print(f"Warning: Sensitive keys found: {exposed}")
return exposed
def compare_configs(self, other_config: Dict[str, Any]) -> Dict[str, Any]:
"""Compare two configurations."""
diff = {
"added": [],
"removed": [],
"changed": []
}
all_keys = set(self.config.keys()) | set(other_config.keys())
for key in all_keys:
if key not in self.config:
diff["added"].append(key)
elif key not in other_config:
diff["removed"].append(key)
elif self.config[key] != other_config[key]:
diff["changed"].append({
"key": key,
"old": self.config[key],
"new": other_config[key]
})
return diff
# Usage
inspector = ConfigInspector(config.to_dict())
inspector.print_summary()
inspector.check_required_keys(['openai_api_key', 'llm_model'])
inspector.check_sensitive_data()
Best Practices
1. Never Commit Secrets
# Use git-secrets or similar tools
git secrets --install
git secrets --register-aws
git secrets --scan
# Pre-commit hook
#!/bin/sh
# .git/hooks/pre-commit
if git diff --cached | grep -E '(api[_-]?key|secret|password|token).*=.*[a-zA-Z0-9]{20,}'; then
echo "Error: Potential secret detected"
exit 1
fi
2. Use Environment-Specific Files
# Always load correct environment file
env = os.getenv("APP_ENV", "development")
env_file = f".env.{env}"
if Path(env_file).exists():
load_dotenv(env_file)
else:
load_dotenv(".env")
3. Validate on Startup
def validate_config_on_startup():
"""Validate configuration when application starts."""
required = [
"OPENAI_API_KEY",
"LLM_MODEL",
"APP_ENV"
]
missing = [key for key in required if not os.getenv(key)]
if missing:
raise ValueError(f"Missing required configuration: {missing}")
print("✓ Configuration validated")
# Call on startup
validate_config_on_startup()
4. Use Type-Safe Configuration
# Use Pydantic or dataclasses for type safety
from pydantic import BaseSettings
class Settings(BaseSettings):
openai_api_key: str
llm_temperature: float = 0.5
cache_enabled: bool = True
settings = Settings() # Validates types automatically
5. Document Configuration
@dataclass
class Config:
"""
Application configuration.
Attributes:
llm_model: LLM model to use (e.g., 'gpt-4o-mini')
llm_temperature: Sampling temperature (0.0-2.0)
- Lower values (0.0-0.3): More deterministic
- Medium values (0.3-0.7): Balanced
- Higher values (0.7-2.0): More creative
cache_ttl: Cache time-to-live in seconds
"""
llm_model: str = "gpt-4o-mini"
llm_temperature: float = 0.5
cache_ttl: int = 3600
6. Separate Configuration Layers
1. Defaults (in code)
2. Configuration files
3. Environment variables
4. Command-line arguments (highest priority)
7. Use Configuration as Code
# config/production.py
from .base import BaseConfig
class ProductionConfig(BaseConfig):
"""Production configuration with overrides."""
def __init__(self):
super().__init__()
# Production-specific overrides
self.debug = False
self.log_level = "WARNING"
self.workers = 8
# Validate production requirements
self._validate_production()
def _validate_production(self):
"""Validate production-specific requirements."""
assert not self.debug, "Debug must be disabled in production"
assert self.workers >= 4, "Minimum 4 workers required in production"