# YAML Configuration: Use YAML for Config

# YAML Configuration: Human-Readable Configs

## Problem

Applications often require complex configuration management. Traditional approaches suffer from:
- **Verbosity**: XML/JSON require excessive syntax
- **Readability**: Hard to parse visually, especially for non-technical users
- **Maintainability**: Changes are error-prone and difficult to track
- **Flexibility**: Limited support for comments, references, and hierarchical data
- **Learning curve**: Steep for configuration authors

## Solution

**YAML (YAML Ain't Markup Language)** provides a human-centric serialization format that prioritizes readability while maintaining structural integrity. It uses indentation-based hierarchy, minimal syntax, and native support for comments.

### Key Advantages

1. **Readability**: Clean, intuitive syntax resembles natural language
2. **Minimal Syntax**: No brackets, braces, or excessive punctuation
3. **Comments**: Full support for inline and block comments
4. **Data Types**: Native support for strings, numbers, booleans, lists, and maps
5. **Hierarchical**: Indentation-based nesting is intuitive
6. **Portability**: Language-agnostic, widely supported across ecosystems

---

## Code Implementation

### 1. Basic YAML Configuration File

```yaml
# application.yaml - Main application configuration

app:
  name: "MyApp"
  version: "1.0.0"
  environment: "production"
  debug: false

server:
  host: "0.0.0.0"
  port: 8080
  timeout: 30
  ssl:
    enabled: true
    cert_path: "/etc/ssl/certs/app.crt"
    key_path: "/etc/ssl/private/app.key"

database:
  primary:
    driver: "postgresql"
    host: "db.example.com"
    port: 5432
    name: "myapp_db"
    username: "app_user"
    password: "${DB_PASSWORD}"  # Environment variable reference
    pool_size: 20
    timeout: 5
  
  replica:
    host: "db-replica.example.com"
    port: 5432
    read_only: true

logging:
  level: "info"  # debug, info, warn, error
  format: "json"
  outputs:
    - type: "console"
      level: "info"
    - type: "file"
      path: "/var/log/app.log"
      level: "warn"
      rotation:
        max_size_mb: 100
        max_backups: 5
        max_age_days: 30

cache:
  enabled: true
  backend: "redis"
  redis:
    host: "cache.example.com"
    port: 6379
    db: 0
    ttl_seconds: 3600

features:
  new_ui: true
  beta_api: false
  analytics: true
  rate_limiting:
    enabled: true
    requests_per_minute: 100

security:
  cors:
    allowed_origins:
      - "https://example.com"
      - "https://app.example.com"
    allowed_methods:
      - "GET"
      - "POST"
      - "PUT"
      - "DELETE"
    max_age: 3600
  
  jwt:
    secret: "${JWT_SECRET}"
    expiration_hours: 24
    refresh_expiration_days: 7
```

### 2. Python Configuration Loader

```python
# config.py - YAML configuration management

import os
import yaml
from typing import Any, Dict, Optional
from pathlib import Path

class ConfigLoader:
    """Load and manage YAML configurations with environment variable support."""
    
    def __init__(self, config_path: str):
        self.config_path = Path(config_path)
        self.config: Dict[str, Any] = {}
        self.load()
    
    def load(self) -> None:
        """Load YAML configuration file."""
        if not self.config_path.exists():
            raise FileNotFoundError(f"Config file not found: {self.config_path}")
        
        with open(self.config_path, 'r') as f:
            raw_config = yaml.safe_load(f)
        
        # Resolve environment variables
        self.config = self._resolve_env_vars(raw_config)
    
    def _resolve_env_vars(self, obj: Any) -> Any:
        """Recursively resolve ${VAR_NAME} environment variable references."""
        if isinstance(obj, dict):
            return {k: self._resolve_env_vars(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [self._resolve_env_vars(item) for item in obj]
        elif isinstance(obj, str):
            # Replace ${VAR_NAME} with environment variable value
            if obj.startswith("${") and obj.endswith("}"):
                var_name = obj[2:-1]
                return os.getenv(var_name, obj)
            return obj
        return obj
    
    def get(self, path: str, default: Any = None) -> Any:
        """Get configuration value using dot notation.
        
        Example: config.get('database.primary.host')
        """
        keys = path.split('.')
        value = self.config
        
        for key in keys:
            if isinstance(value, dict):
                value = value.get(key)
                if value is None:
                    return default
            else:
                return default
        
        return value
    
    def get_section(self, section: str) -> Dict[str, Any]:
        """Get entire configuration section."""
        return self.config.get(section, {})
    
    def __getitem__(self, key: str) -> Any:
        """Dictionary-style access."""
        return self.config[key]
    
    def __repr__(self) -> str:
        return f"ConfigLoader({self.config_path})"


# Usage Example
if __name__ == "__main__":
    # Set environment variables
    os.environ['DB_PASSWORD'] = 'secure_password_123'
    os.environ['JWT_SECRET'] = 'jwt_secret_key_xyz'
    
    # Load configuration
    config = ConfigLoader('application.yaml')
    
    # Access values
    print(f"App Name: {config.get('app.name')}")
    print(f"Server Port: {config.get('server.port')}")
    print(f"DB Host: {config.get('database.primary.host')}")
    print(f"DB Password: {config.get('database.primary.password')}")
    print(f"Logging Level: {config.get('logging.level')}")
    
    # Get entire sections
    server_config = config.get_section('server')
    print(f"\nServer Config: {server_config}")
```

### 3. Environment-Specific Configurations

```yaml
# config.base.yaml - Base configuration

app:
  name: "MyApp"
  version: "1.0.0"

server:
  timeout: 30

database:
  pool_size: 20
```

```yaml
# config.development.yaml - Development overrides

app:
  environment: "development"
  debug: true

server:
  host: "localhost"
  port: 3000

logging:
  level: "debug"

database:
  primary:
    host: "localhost"
    port: 5432
```

```yaml
# config.production.yaml - Production overrides

app:
  environment: "production"
  debug: false

server:
  host: "0.0.0.0"
  port: 8080

logging:
  level: "warn"

database:
  pool_size: 50
```

```python
# config_manager.py - Multi-environment configuration

import yaml
from pathlib import Path
from typing import Dict, Any

class ConfigManager:
    """Manage environment-specific configurations."""
    
    def __init__(self, env: str = "development"):
        self.env = env
        self.config = self._load_configs()
    
    def _load_configs(self) -> Dict[str, Any]:
        """Load base config and merge environment-specific overrides."""
        base_path = Path("config.base.yaml")
        env_path = Path(f"config.{self.env}.yaml")
        
        # Load base configuration
        with open(base_path) as f:
            config = yaml.safe_load(f)
        
        # Merge environment-specific configuration
        if env_path.exists():
            with open(env_path) as f:
                env_config = yaml.safe_load(f)
                config = self._deep_merge(config, env_config)
        
        return config
    
    @staticmethod
    def _deep_merge(base: Dict, override: Dict) -> Dict:
        """Deep merge override config into base config."""
        result = base.copy()
        for key, value in override.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = ConfigManager._deep_merge(result[key], value)
            else:
                result[key] = value
        return result
    
    def get(self, path: str, default: Any = None) -> Any:
        """Get configuration value using dot notation."""
        keys = path.split('.')
        value = self.config
        for key in keys:
            value = value.get(key) if isinstance(value, dict) else None
            if value is None:
                return default
        return value
```

### 4. Validation Schema

```yaml
# config.schema.yaml - Configuration validation schema

app:
  name:
    type: string
    required: true
    min_length: 1
  version:
    type: string
    required: true
    pattern: '^\d+\.\d+\.\d+$'
  environment:
    type: string
    required: true
    enum: [development, staging, production]

server:
  port:
    type: integer
    required: true
    minimum: 1024
    maximum: 65535
  timeout:
    type: integer
    required: false
    minimum: 1

database:
  primary:
    host:
      type: string
      required: true
    port:
      type: integer
      required: true
      minimum: 1
      maximum: 65535
    pool_size:
      type: integer
      required: false
      minimum: 1
      maximum: 1000
```

---

## Summary

YAML configurations provide a **human-readable, maintainable solution** for application settings. By combining YAML's intuitive syntax with programmatic loaders, environment variable resolution, and validation, you create robust configuration systems that are easy to understand, modify, and deploy across different environments.
