# Configuration Management Patterns

## Environment-Based Configuration
```python
import os
from pathlib import Path
from typing import Optional
from dataclasses import dataclass
import json

@dataclass
class Config:
    """Application configuration."""
    debug: bool = False
    database_url: str = "sqlite:///app.db"
    api_key: Optional[str] = None
    timeout: int = 30
    max_retries: int = 3

    @classmethod
    def from_env(cls) -> "Config":
        """Load config from environment variables."""
        return cls(
            debug=os.getenv('DEBUG', '').lower() == 'true',
            database_url=os.getenv('DATABASE_URL', cls.database_url),
            api_key=os.getenv('API_KEY'),
            timeout=int(os.getenv('TIMEOUT', cls.timeout)),
            max_retries=int(os.getenv('MAX_RETRIES', cls.max_retries))
        )
```

## Configuration Files

### 1. JSON Configuration
```python
class JsonConfig:
    def __init__(self, config_path: Path):
        self.config_path = Path(config_path)
        self._config = {}
        self.load()

    def load(self):
        """Load configuration from JSON file."""
        if self.config_path.exists():
            with open(self.config_path) as f:
                self._config = json.load(f)

    def get(self, key: str, default=None):
        """Get config value with dot notation support."""
        keys = key.split('.')
        value = self._config

        for k in keys:
            if isinstance(value, dict):
                value = value.get(k)
            else:
                return default

        return value if value is not None else default

# Usage
config = JsonConfig('config.json')
db_host = config.get('database.host', 'localhost')
```

### 2. TOML Configuration
```python
import toml

class TomlConfig:
    def __init__(self, config_file='config.toml'):
        self.config_file = config_file
        self.config = self._load_config()

    def _load_config(self) -> dict:
        """Load and merge configurations."""
        # Default config
        config = {
            'app': {
                'name': 'MyApp',
                'version': '1.0.0'
            },
            'server': {
                'host': '0.0.0.0',
                'port': 8000
            }
        }

        # Load from file
        if Path(self.config_file).exists():
            with open(self.config_file) as f:
                file_config = toml.load(f)
                config = deep_merge(config, file_config)

        # Override with environment
        env_config = self._load_env_overrides()
        return deep_merge(config, env_config)
```

## Configuration Validation

### 1. Schema Validation
```python
from typing import Any
from dataclasses import dataclass, field

@dataclass
class DatabaseConfig:
    host: str
    port: int
    username: str
    password: str = field(repr=False)  # Hide in logs

    def __post_init__(self):
        if self.port < 1 or self.port > 65535:
            raise ValueError(f"Invalid port: {self.port}")
        if not self.host:
            raise ValueError("Database host required")

@dataclass
class AppConfig:
    database: DatabaseConfig
    debug: bool = False
    log_level: str = "INFO"

    def __post_init__(self):
        valid_levels = {'DEBUG', 'INFO', 'WARNING', 'ERROR'}
        if self.log_level not in valid_levels:
            raise ValueError(f"Invalid log level: {self.log_level}")
```

### 2. Type Conversion
```python
def parse_bool(value: Any) -> bool:
    """Parse boolean from various formats."""
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        return value.lower() in ('true', 'yes', '1', 'on')
    return bool(value)

def parse_list(value: Any, sep=',') -> list:
    """Parse list from string or return list as-is."""
    if isinstance(value, list):
        return value
    if isinstance(value, str):
        return [x.strip() for x in value.split(sep) if x.strip()]
    return []

# Usage
ALLOWED_HOSTS = parse_list(os.getenv('ALLOWED_HOSTS', 'localhost'))
DEBUG = parse_bool(os.getenv('DEBUG', 'false'))
```

## Configuration Patterns

### 1. Layered Configuration
```python
class LayeredConfig:
    """Configuration with multiple sources and precedence."""

    def __init__(self):
        self.layers = []

    def add_file(self, filepath: Path, priority: int = 0):
        """Add configuration file layer."""
        if filepath.exists():
            with open(filepath) as f:
                config = json.load(f)
                self.layers.append((priority, config))
                self.layers.sort(key=lambda x: x[0])

    def add_env(self, prefix: str = '', priority: int = 10):
        """Add environment variables layer."""
        env_config = {}
        for key, value in os.environ.items():
            if key.startswith(prefix):
                # Convert MY_APP_KEY to my_app.key
                clean_key = key[len(prefix):].lower().replace('_', '.')
                env_config[clean_key] = value
        self.layers.append((priority, env_config))
        self.layers.sort(key=lambda x: x[0])

    def get(self, key: str, default=None):
        """Get value with highest priority."""
        for _, config in reversed(self.layers):
            value = self._get_nested(config, key)
            if value is not None:
                return value
        return default
```

### 2. Secret Management
```python
import keyring
from cryptography.fernet import Fernet

class SecretConfig:
    """Secure secret management."""

    def __init__(self, app_name: str):
        self.app_name = app_name
        self._cache = {}

    def get_secret(self, key: str) -> Optional[str]:
        """Get secret from secure storage."""
        # Check cache first
        if key in self._cache:
            return self._cache[key]

        # Try environment variable
        env_key = f"{self.app_name.upper()}_{key.upper()}"
        value = os.getenv(env_key)

        if not value:
            # Try system keyring
            value = keyring.get_password(self.app_name, key)

        if value:
            self._cache[key] = value

        return value

    def set_secret(self, key: str, value: str):
        """Store secret securely."""
        keyring.set_password(self.app_name, key, value)
        self._cache[key] = value
```

### 3. Dynamic Reloading
```python
import signal
import threading

class ReloadableConfig:
    """Configuration that can reload without restart."""

    def __init__(self, config_file: str):
        self.config_file = config_file
        self._config = {}
        self._lock = threading.Lock()
        self.reload()

        # Setup signal handler
        signal.signal(signal.SIGHUP, self._handle_reload)

    def reload(self):
        """Reload configuration from file."""
        try:
            with open(self.config_file) as f:
                new_config = json.load(f)

            with self._lock:
                self._config = new_config

            logger.info("Configuration reloaded")
        except Exception as e:
            logger.error(f"Failed to reload config: {e}")

    def _handle_reload(self, signum, frame):
        """Handle reload signal."""
        self.reload()

    def get(self, key: str, default=None):
        """Thread-safe config access."""
        with self._lock:
            return self._config.get(key, default)
```

## Testing Configuration
```python
import pytest
from unittest.mock import patch

def test_config_from_env():
    with patch.dict(os.environ, {
        'DEBUG': 'true',
        'DATABASE_URL': 'postgres://localhost/test',
        'API_KEY': 'secret123'
    }):
        config = Config.from_env()
        assert config.debug is True
        assert config.database_url == 'postgres://localhost/test'
        assert config.api_key == 'secret123'

def test_config_validation():
    with pytest.raises(ValueError, match="Invalid port"):
        DatabaseConfig(host="localhost", port=70000,
                      username="user", password="pass")

def test_layered_config():
    config = LayeredConfig()
    config.add_file(Path('default.json'), priority=0)
    config.add_env('MY_APP_', priority=10)

    # Environment overrides file
    assert config.get('database.host') == os.getenv('MY_APP_DATABASE_HOST')
```
