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')}")
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:
database.host
)Configuration management is a fundamental challenge in application development. This solution addresses several common pain points:
config_manager.py
)app_config.json
:
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"api": {
"host": "0.0.0.0",
"port": 8000,
"debug": false
}
}
Run normally: python config_manager.py
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.