import os
from typing import Any, Dict, List, Optional, Union
import json
class EnvConfigLoader:
"""
A utility class for loading environment variables with automatic type casting.
Supports strings, integers, floats, booleans, lists, and JSON objects.
"""
@staticmethod
def _cast_value(value: str, target_type: str) -> Any:
"""Cast string value to the specified type."""
value = value.strip()
if target_type == 'str':
return value
elif target_type == 'int':
return int(value)
elif target_type == 'float':
return float(value)
elif target_type == 'bool':
return value.lower() in ('true', '1', 'yes', 'on')
elif target_type == 'list':
# Try to parse as JSON array first
try:
parsed = json.loads(value)
if isinstance(parsed, list):
return parsed
except json.JSONDecodeError:
pass
# Fall back to comma-separated list
return [item.strip() for item in value.split(',') if item.strip()]
elif target_type == 'json':
try:
return json.loads(value)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in environment variable: {e}")
else:
raise ValueError(f"Unsupported type: {target_type}")
@classmethod
def load_config(cls, config_schema: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
"""
Load configuration from environment variables based on a schema.
Args:
config_schema: A dictionary defining the configuration schema.
Format: {'VAR_NAME': {'type': 'str|int|float|bool|list|json',
'default': optional_default_value,
'required': True/False}}
Returns:
Dictionary with loaded configuration values.
"""
config = {}
for var_name, var_config in config_schema.items():
var_type = var_config.get('type', 'str')
default = var_config.get('default', None)
required = var_config.get('required', False)
# Get value from environment
value = os.environ.get(var_name, default)
# Handle missing required variables
if value is None and required:
raise ValueError(f"Required environment variable '{var_name}' is not set")
# Cast value if present
if value is not None:
try:
config[var_name] = cls._cast_value(str(value), var_type)
except Exception as e:
raise ValueError(f"Error parsing environment variable '{var_name}': {e}")
else:
config[var_name] = None
return config
# Example usage
if __name__ == "__main__":
# Define configuration schema
config_schema = {
'DEBUG': {
'type': 'bool',
'default': False,
'required': False
},
'PORT': {
'type': 'int',
'default': 8000,
'required': False
},
'DATABASE_URL': {
'type': 'str',
'required': True
},
'ALLOWED_HOSTS': {
'type': 'list',
'default': 'localhost,127.0.0.1',
'required': False
},
'FEATURE_FLAGS': {
'type': 'json',
'required': False
}
}
# Load configuration (in a real app, environment variables would be set externally)
# For demonstration, we'll set some test values
os.environ['DATABASE_URL'] = 'postgresql://user:pass@localhost/mydb'
os.environ['DEBUG'] = 'true'
os.environ['PORT'] = '3000'
os.environ['FEATURE_FLAGS'] = '{"new_ui": true, "analytics": false}'
# Load the configuration
config = EnvConfigLoader.load_config(config_schema)
# Print results
print("Loaded Configuration:")
for key, value in config.items():
print(f" {key}: {value} ({type(value).__name__})")
This snippet provides a robust configuration loader that reads environment variables and automatically converts them to appropriate Python data types. It’s particularly useful for applications following the 12-factor app methodology, where configuration is stored in environment variables.
The EnvConfigLoader class offers several key features:
str)int)float)bool)list) - from JSON arrays or comma-separated stringsjson)Schema Validation: Defines expected configuration variables with type information, default values, and required/optional status.
Error Handling: Provides clear error messages for missing required variables or parsing failures.
Environment variables are the standard way to configure applications in containerized environments, cloud platforms, and production deployments. However, environment variables are always strings, which means developers need to manually convert them to appropriate data types.
This utility eliminates boilerplate code for configuration management and reduces errors from manual parsing. It’s especially valuable for:
Save the code to a file (e.g., config_loader.py)
export DATABASE_URL="postgresql://user:pass@localhost/mydb"
export DEBUG="true"
export PORT="3000"
export ALLOWED_HOSTS="localhost,127.0.0.1,example.com"
export FEATURE_FLAGS='{"new_ui": true, "analytics": false}'
python config_loader.py
from config_loader import EnvConfigLoader
schema = {
'DATABASE_URL': {'type': 'str', 'required': True},
'DEBUG': {'type': 'bool', 'default': False},
'PORT': {'type': 'int', 'default': 8000}
}
config = EnvConfigLoader.load_config(schema)
The output will show the loaded configuration with properly typed values, demonstrating how string environment variables are automatically converted to their appropriate Python types.