Python Snippets

Smart Configuration Manager with Environment Variable Override

import os
import json
from typing import Any, Dict, Optional, Union
from pathlib import Path

class ConfigManager:
    """
    A smart configuration manager that loads settings from JSON files
    and allows environment variable overrides for deployment flexibility.
    """
    
    def __init__(self, config_file: str = "config.json"):
        self.config_file = Path(config_file)
        self._config = {}
        self._load_config()
    
    def _load_config(self):
        """Load configuration from JSON file"""
        if self.config_file.exists():
            with open(self.config_file, 'r') as f:
                self._config = json.load(f)
        else:
            # Default configuration
            self._config = {
                "database": {
                    "host": "localhost",
                    "port": 5432,
                    "name": "myapp",
                    "user": "admin",
                    "password": "secret"
                },
                "api": {
                    "host": "0.0.0.0",
                    "port": 8000,
                    "debug": False
                },
                "features": {
                    "enable_cache": True,
                    "max_connections": 100
                }
            }
    
    def get(self, key: str, default: Any = None) -> Any:
        """
        Get configuration value with environment variable override.
        Environment variables use double underscore notation.
        Example: DATABASE__HOST for config['database']['host']
        """
        # Check for environment variable override first
        env_key = key.upper().replace('.', '__')
        env_value = os.environ.get(env_key)
        
        if env_value is not None:
            # Try to convert to appropriate type
            return self._convert_type(env_value)
        
        # Navigate nested dictionary using dot notation
        keys = key.split('.')
        value = self._config
        
        try:
            for k in keys:
                value = value[k]
            return value
        except (KeyError, TypeError):
            return default
    
    def _convert_type(self, value: str) -> Union[str, int, float, bool]:
        """Convert string value to appropriate Python type"""
        # Handle boolean values
        if value.lower() in ('true', 'false'):
            return value.lower() == 'true'
        
        # Handle integer values
        try:
            if '.' not in value:
                return int(value)
        except ValueError:
            pass
        
        # Handle float values
        try:
            return float(value)
        except ValueError:
            pass
        
        # Return as string if no conversion possible
        return value
    
    def reload(self):
        """Reload configuration from file"""
        self._load_config()
    
    def as_dict(self) -> Dict[str, Any]:
        """Return entire configuration as dictionary"""
        return self._config.copy()

# Usage example
if __name__ == "__main__":
    # Create config manager
    config = ConfigManager("app_config.json")
    
    # Get values (will check for DATABASE__HOST environment variable first)
    db_host = config.get("database.host", "localhost")
    db_port = config.get("database.port", 5432)
    debug_mode = config.get("api.debug", False)
    
    print(f"Database: {db_host}:{db_port}")
    print(f"Debug mode: {debug_mode}")
    
    # Example with environment override
    # Run with: DATABASE__HOST=production-db.example.com python script.py
    print(f"Actual DB host: {config.get('database.host')}")

What This Code Does

This snippet implements a smart configuration manager that provides a clean interface for managing application settings. It solves the common problem of handling configuration in different deployment environments (development, staging, production) by combining JSON file-based configuration with environment variable overrides.

The key features include:

Why It’s Useful

Configuration management is a fundamental challenge in application development. This solution addresses several common pain points:

  1. Environment Flexibility: Deploy the same code to different environments without code changes
  2. Security: Sensitive values (passwords, API keys) can be injected via environment variables
  3. Type Safety: Automatic type conversion prevents runtime errors from string-only configs
  4. Developer Experience: Clean, intuitive API with dot notation and default values

How to Run It

  1. Save the code to a file (e.g., config_manager.py)
  2. Create a configuration file app_config.json:
    {
      "database": {
     "host": "localhost",
     "port": 5432,
     "name": "myapp"
      },
      "api": {
     "host": "0.0.0.0",
     "port": 8000,
     "debug": false
      }
    }
    
  3. Run normally: python config_manager.py

  4. Override with environment variables:
    DATABASE__HOST=production-db.example.com \
    API__DEBUG=true \
    python config_manager.py
    

The environment variables use double underscores to represent nested structure - DATABASE__HOST maps to config['database']['host']. This pattern is widely adopted by popular frameworks like Django and FastAPI.