import os
import json
from typing import Any, Dict, Optional
from pathlib import Path
class ConfigManager:
"""
A smart configuration manager that loads settings from multiple sources
with environment-based overrides and type validation.
"""
def __init__(self, config_file: str = "config.json"):
self.config_file = config_file
self._config = {}
self._load_config()
def _load_config(self):
"""Load configuration from file and environment variables"""
# Load from JSON file
if os.path.exists(self.config_file):
with open(self.config_file, 'r') as f:
file_config = json.load(f)
self._config.update(file_config)
# Load environment variables with prefix
env_prefix = "APP_"
for key, value in os.environ.items():
if key.startswith(env_prefix):
config_key = key[len(env_prefix):].lower()
# Try to parse as JSON for complex types, fallback to string
try:
self._config[config_key] = json.loads(value)
except json.JSONDecodeError:
self._config[config_key] = value
def get(self, key: str, default: Any = None, value_type: type = None) -> Any:
"""
Get configuration value with optional type conversion and default value.
Args:
key: Configuration key
default: Default value if key not found
value_type: Type to convert the value to (int, float, bool, list, dict)
Returns:
Configuration value
"""
value = self._config.get(key, default)
if value_type is not None and value is not None:
try:
if value_type == bool:
if isinstance(value, str):
return value.lower() in ('true', '1', 'yes', 'on')
return bool(value)
elif value_type == list:
if isinstance(value, str):
return json.loads(value)
return list(value)
elif value_type == dict:
if isinstance(value, str):
return json.loads(value)
return dict(value)
else:
return value_type(value)
except (ValueError, TypeError, json.JSONDecodeError):
# Return default if conversion fails
return default
return value
def get_nested(self, key_path: str, separator: str = '.', default: Any = None) -> Any:
"""
Get nested configuration value using dot notation.
Args:
key_path: Dot-separated path to nested value (e.g., 'database.host')
separator: Separator for nested keys
default: Default value if key not found
Returns:
Nested configuration value
"""
keys = key_path.split(separator)
current = self._config
try:
for key in keys:
current = current[key]
return current
except (KeyError, TypeError):
return default
def reload(self):
"""Reload configuration from sources"""
self._config = {}
self._load_config()
def get_all(self) -> Dict[str, Any]:
"""Get all configuration values"""
return self._config.copy()
# Example usage
if __name__ == "__main__":
# Create sample config file
sample_config = {
"database": {
"host": "localhost",
"port": 5432,
"username": "user",
"password": "secret"
},
"api": {
"timeout": 30,
"retries": 3
},
"debug": False,
"features": ["auth", "logging"]
}
with open("config.json", "w") as f:
json.dump(sample_config, f, indent=2)
# Initialize config manager
config = ConfigManager()
# Usage examples
print("Database host:", config.get_nested("database.host"))
print("API timeout:", config.get("timeout", 30, int))
print("Debug mode:", config.get("debug", False, bool))
print("Features:", config.get("features", [], list))
# Clean up
os.remove("config.json")
This code snippet provides a comprehensive configuration manager that handles one of the most common challenges in application development: managing configuration settings across different environments. It’s especially useful in modern cloud deployments and microservices architectures.
APP_
prefix to avoid conflicts with system variablesconfig_manager.py
)config.json
file with your application settings (optional)APP_
prefix (e.g., APP_DEBUG=true
)ConfigManager
class in your applicationThe manager will automatically load settings from both sources, with environment variables taking precedence over file-based configuration. This pattern is ideal for 12-factor applications where configuration is stored in the environment.