Examples
This page provides practical examples of using Configr in different scenarios. Each example demonstrates key features and patterns to help you make the most of the library in your projects.
Basic Configuration
Simple Application Configuration
This example shows a basic application configuration setup.
# app_config.py
from configr import config_class, ConfigBase
@config_class(file_name="app_settings.json")
class AppConfig:
app_name: str
version: str
debug: bool = False
log_level: str = "INFO"
max_connections: int = 100
# Load the configuration
app_config = ConfigBase.load(AppConfig)
# Use the configuration
print(f"Starting {app_config.app_name} v{app_config.version}")
print(f"Debug mode: {app_config.debug}")
print(f"Log level: {app_config.log_level}")
Configuration file (_config/app_settings.json):
Nested Configuration
Database and Logging Configuration
This example demonstrates nested configuration structures.
# config.py
from configr import config_class, ConfigBase
from dataclasses import dataclass, field
@dataclass
class DatabaseConfig:
username: str
password: str
database: str
host: str = "localhost"
port: int = 5432
ssl_mode: str = "prefer"
def get_connection_string(self):
"""Generate a database connection string."""
return f"postgresql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}?sslmode={self.ssl_mode}"
@dataclass
class LoggingConfig:
level: str = "INFO"
file: str = None
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
max_size: int = 10485760 # 10MB
backup_count: int = 5
@config_class(file_name="server_config.json")
class ServerConfig:
name: str
host: str = "0.0.0.0"
port: int = 8000
workers: int = 4
database: DatabaseConfig = field(default_factory=DatabaseConfig)
logging: LoggingConfig = None
def __post_init__(self):
# Validation
if self.port < 1024 or self.port > 65535:
raise ValueError(f"Invalid port: {self.port}")
if self.workers < 1:
raise ValueError(f"Workers must be at least 1, got {self.workers}")
# main.py
from config import ServerConfig, ConfigBase
server_config = ConfigBase.load(ServerConfig)
# Access nested configuration
db_conn_string = server_config.database.get_connection_string()
# If server_config.logging is None, it will try to create an instance of LoggingConfig
# with default values if possible, so log level is set to INFO in that case.
log_level = server_config.logging.level
print(f"Server: {server_config.name} running on {server_config.host}:{server_config.port}")
print(f"Database connection: {db_conn_string}")
print(f"Log level: {log_level}")
Configuration file (_config/server_config.json):
{
"name": "ProductionServer",
"port": 8080,
"workers": 8,
"database": {
"host": "db.example.com",
"username": "admin",
"password": "secure_password",
"database": "production_db",
"ssl_mode": "require"
},
"logging": {
"level": "WARNING",
"file": "/var/log/myapp.log",
"backup_count": 10
}
}
Multiple Configuration Files
Separate Configs for different Components
This example shows how to work with multiple configuration files for different components of your application.
# configs.py
from configr import config_class, ConfigBase
@config_class(file_name="database.json")
class DatabaseConfig:
username: str
password: str
database: str
host: str = 'localhost'
port: int = 5432
max_connections: int = 100
@config_class(file_name="redis.json")
class RedisConfig:
host: str = "localhost"
port: int = 6379
db: int = 0
password: str = None
socket_timeout: int = 5
@config_class(file_name="app.json")
class AppConfig:
debug: bool = False
log_level: str = "INFO"
secret_key: str
allowed_hosts: list[str] = None
# Access specific configurations
db_config = ConfigBase.load(DatabaseConfig)
redis_config = ConfigBase.load(RedisConfig)
app_config = ConfigBase.load(AppConfig)
print(f"Database: {db_config.host}:{db_config.port}/{db_config.database}")
print(f"Redis: {redis_config.host}:{redis_config.port}")
print(f"App debug mode: {app_config.debug}")
Configuration files:
_config/database.json:
{
"host": "db.example.com",
"username": "admin",
"password": "secure_password",
"database": "myapp_db",
"max_connections": 50
}
_config/redis.json:
_config/app.json:
{
"debug": false,
"log_level": "WARNING",
"secret_key": "very-secret-key-123",
"allowed_hosts": [
"example.com",
"www.example.com"
]
}
Environment Variable Configuration
Loading Configuration from Environment Variables
This example shows how to use the built-in environment variable loader to configure your application.
# config.py
from configr import config_class, ConfigBase
from dataclasses import dataclass
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = "postgres"
password: str
database: str
# Use the EnvVarConfigLoader by not specifying a file_name
# and not having a corresponding config file for the other
# loaders (e.g. no app_config.yaml nor app_config.json)
@config_class()
class AppConfig:
app_name: str
debug: bool = False
log_level: str = "INFO"
database: DatabaseConfig = None
# Load from environment variables
app_config = ConfigBase.load(AppConfig)
print(f"App: {app_config.app_name}")
print(f"Debug: {app_config.debug}")
print(f"Database: {app_config.database.host}:{app_config.database.port}/{app_config.database.database}")
Setting environment variables:
# Set top-level configuration
export APPCONFIG_APP_NAME="MyApp"
export APPCONFIG_DEBUG="true"
export APPCONFIG_LOG_LEVEL="DEBUG"
# Set nested configuration
export APPCONFIG_DATABASE_HOST="db.example.com"
export APPCONFIG_DATABASE_PORT="5432"
export APPCONFIG_DATABASE_USERNAME="admin"
export APPCONFIG_DATABASE_PASSWORD="secure_password"
export APPCONFIG_DATABASE_DATABASE="production_db"
The environment variable loader automatically converts:
- Boolean strings ("true", "false", "1", "0") to boolean values
- Numeric strings to integers or floats as appropriate
- Nested dataclasses using underscore-separated prefixes
.env File Configuration
Loading Configuration from .env Files
This example shows how to use .env files for configuration management, which is particularly useful for local development and containerized applications.
# config.py
from configr import config_class, ConfigBase
from dataclasses import dataclass
from typing import Optional
@dataclass
class DatabaseCredentials:
username: str
password: str
@dataclass
class RedisConfig:
host: str = "localhost"
port: int = 6379
password: Optional[str] = None
@config_class(file_name=".env")
class AppConfig:
debug: bool = False
log_level: str = "INFO"
secret_key: str
database_url: str
database_credentials: Optional[DatabaseCredentials] = None
redis: Optional[RedisConfig] = None
# Load from .env file (requires python-dotenv)
app_config = ConfigBase.load(AppConfig)
print(f"App debug mode: {app_config.debug}")
print(f"Log level: {app_config.log_level}")
print(f"Database URL: {app_config.database_url}")
if app_config.database_credentials:
print(f"Database user: {app_config.database_credentials.username}")
if app_config.redis:
print(f"Redis host: {app_config.redis.host}:{app_config.redis.port}")
Configuration file (_config/.env):
# Application settings (uses APP_ prefix from class name AppConfig)
APP_DEBUG=true
APP_LOG_LEVEL=DEBUG
APP_SECRET_KEY=your-secret-key-here
APP_DATABASE_URL=postgresql://localhost:5432/myapp
# Database credentials (nested dataclass)
APP_DATABASE_CREDENTIALS_USERNAME=admin
APP_DATABASE_CREDENTIALS_PASSWORD=secure_password
# Redis configuration (nested dataclass)
APP_REDIS_HOST=redis.example.com
APP_REDIS_PORT=6379
APP_REDIS_PASSWORD=redis_secret
Key features of .env configuration:
- Environment variable format: Uses the same naming convention as EnvVarConfigLoader
- Nested dataclass support: Handles complex nested structures with underscore separation
- Type conversion: Automatically converts strings to appropriate types (bool, int, etc.)
- Local development friendly: Perfect for keeping sensitive data out of version control
Installation requirement:
Environment-Specific Configuration
Dynamic Configuration Based on Environment
This example demonstrates loading different configurations based on the environment.
# config.py
import os
from configr import config_class, ConfigBase
# Determine environment
ENV = os.environ.get("APP_ENV", "development")
@config_class(file_name=f"app.{ENV}.json")
class AppConfig:
debug: bool = ENV != "production"
log_level: str = "DEBUG" if ENV != "production" else "INFO"
database_url: str
redis_url: str = None
secret_key: str
allowed_hosts: list[str] = None
# app.py
from config import AppConfig, ENV
from configr import ConfigBase
config = ConfigBase.load(AppConfig)
print(f"Running in {ENV} environment")
print(f"Debug mode: {config.debug}")
print(f"Log level: {config.log_level}")
print(f"Database URL: {config.database_url}")
Configuration files:
_config/app.development.json:
{
"debug": true,
"log_level": "DEBUG",
"database_url": "postgresql://dev:dev@localhost/dev_db",
"secret_key": "dev-secret-key",
"allowed_hosts": [
"localhost",
"127.0.0.1"
]
}
_config/app.production.json:
{
"debug": false,
"log_level": "WARNING",
"database_url": "postgresql://user:pass@db.example.com/prod_db",
"redis_url": "redis://redis.example.com:6379/0",
"secret_key": "production-secret-key-very-secure",
"allowed_hosts": [
"example.com",
"www.example.com",
"api.example.com"
]
}
List of Dataclasses
Service Configuration with Multiple Endpoints
This example shows how to configure a list of service endpoints.
# services_config.py
from configr import config_class, ConfigBase
from dataclasses import dataclass
@dataclass
class ServiceEndpoint:
name: str
url: str
timeout: int = 30
retries: int = 3
api_key: str = None
@config_class(file_name="services.json")
class ServicesConfig:
base_timeout: int = 60
default_retries: int = 5
endpoints: list[ServiceEndpoint]
def get_endpoint(self, name):
"""Find an endpoint by name."""
for endpoint in self.endpoints:
if endpoint.name == name:
return endpoint
return None
# Load the configuration
services_config = ConfigBase.load(ServicesConfig)
# Get a specific endpoint
auth_service = services_config.get_endpoint("authentication")
if auth_service:
print(f"Auth service URL: {auth_service.url}")
print(f"Auth service timeout: {auth_service.timeout}s")
# Iterate through all endpoints
print("Available services:")
for endpoint in services_config.endpoints:
print(f"- {endpoint.name}: {endpoint.url}")
Configuration file (_config/services.json):
{
"base_timeout": 30,
"endpoints": [
{
"name": "authentication",
"url": "https://auth.example.com/api",
"timeout": 10,
"api_key": "auth-api-key-123"
},
{
"name": "storage",
"url": "https://storage.example.com/api",
"timeout": 60,
"retries": 5
},
{
"name": "analytics",
"url": "https://analytics.example.com/api",
"api_key": "analytics-api-key-456"
}
]
}
Custom Loaders
TOML Configuration loader
This example demonstrates creating, registering and using a custom loader for TOML files.
# toml_loader.py
from typing import Any, TypeVar
from configr.loaders.loader_base import FileConfigLoader
T = TypeVar('T')
class TOMLConfigLoader(FileConfigLoader):
"""Loader for TOML configuration files."""
ext: list[str] = ['.toml'] # Supported file extensions
@classmethod
def load(cls, name: str, config_class: type[T] = None) -> dict[str, Any]:
"""Load TOML configuration from the specified path."""
try:
import toml
except ImportError:
raise ImportError("The 'toml' package is required for TOML support. Install with 'pip install toml'.")
config_file_path = cls._get_config_file_path(name)
with open(config_file_path) as f:
return toml.load(f)
# config.py
from configr import config_class, ConfigBase
from toml_loader import TOMLConfigLoader
# Register the custom loader
ConfigBase.add_loader(TOMLConfigLoader)
# Now you can use TOML files with your config classes
@config_class(file_name="app_config.toml")
class AppConfig:
name: str
version: str
authors: list[str]
debug: bool = False
class Dependencies:
python: str
requests: str
dependencies: Dependencies
# Load TOML configuration
app_config = ConfigBase.load(AppConfig)
print(f"App: {app_config.name} v{app_config.version}")
print(f"Authors: {', '.join(app_config.authors)}")
print(f"Python version: {app_config.dependencies.python}")
Configuration file (_config/app_config.toml):
name = "MyTOMLApp"
version = "0.1.0"
authors = ["Jane Doe", "John Smith"]
debug = true
[dependencies]
python = ">=3.9"
requests = "^2.28.0"
Error Handling
Robust Configuration Loading
This example shows how to handle various configuration errors gracefully.
# config.py
from configr import config_class, ConfigBase, ConfigFileNotFoundError, ConfigValidationError
@config_class(file_name="app_settings.json")
class AppSettings:
debug: bool = False
log_level: str = "INFO"
port: int = 8000
def __post_init__(self):
valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
if self.log_level not in valid_log_levels:
raise ValueError(f"Invalid log level: {self.log_level}. Must be one of {valid_log_levels}")
if self.port < 1024 or self.port > 65535:
raise ValueError(f"Invalid port: {self.port}. Must be between 1024 and 65535")
def load_settings():
"""Load settings with robust error handling."""
try:
return ConfigBase.load(AppSettings)
except ConfigFileNotFoundError as e:
print(f"Configuration file not found: {e}")
print("Using default settings")
return AppSettings()
except ConfigValidationError as e:
print(f"Configuration validation failed: {e}")
print("Please check your configuration file format and types")
raise
except ValueError as e:
print(f"Invalid configuration value: {e}")
print("Please check your configuration settings")
raise
except Exception as e:
print(f"Unexpected error loading configuration: {e}")
print("Using default settings as fallback")
return AppSettings()
# app.py
from config import load_settings
# Load settings with error handling
settings = load_settings()
# Use the settings
print(f"Starting server on port {settings.port}")
print(f"Debug mode: {settings.debug}")
print(f"Log level: {settings.log_level}")
Web Application Example
Flask Web App Configuration
This example demonstrates using Configr with a Flask web application.
# config.py
import os
from configr import config_class, ConfigBase, ConfigFileNotFoundError
from dataclasses import dataclass
from typing import Any
# Determine environment
ENV = os.environ.get("FLASK_ENV", "development")
@dataclass
class DatabaseConfig:
url: str
pool_size: int = 10
pool_recycle: int = 3600
pool_timeout: int = 30
@dataclass
class CacheConfig:
type: str = "redis"
url: str = "redis://localhost:6379/0"
timeout: int = 300
@config_class(file_name=f"flask_app.{ENV}.json")
class FlaskConfig:
# Flask settings
secret_key: str
debug: bool = ENV != "production"
testing: bool = ENV == "testing"
host: str = "127.0.0.1"
port: int = 5000
# Database settings
database: DatabaseConfig = None
# Cache settings
cache: CacheConfig = None
# CORS settings
cors_origins: list[str] = None
# Other settings
upload_folder: str = "/tmp/uploads"
max_content_length: int = 16 * 1024 * 1024 # 16 MB
# Custom app settings
app_name: str = "Flask App"
admin_emails: list[str] = None
def to_flask_config(self) -> dict[str, Any]:
"""Convert to a dictionary for Flask configuration."""
# Extract top-level fields first
config_dict = {
"SECRET_KEY": self.secret_key,
"DEBUG": self.debug,
"TESTING": self.testing,
"MAX_CONTENT_LENGTH": self.max_content_length,
"UPLOAD_FOLDER": self.upload_folder,
"APP_NAME": self.app_name,
"ADMIN_EMAILS": self.admin_emails or []
}
# Add database settings
if self.database:
config_dict["SQLALCHEMY_DATABASE_URI"] = self.database.url
config_dict["SQLALCHEMY_POOL_SIZE"] = self.database.pool_size
config_dict["SQLALCHEMY_POOL_RECYCLE"] = self.database.pool_recycle
config_dict["SQLALCHEMY_POOL_TIMEOUT"] = self.database.pool_timeout
config_dict["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# Add cache settings if present
if self.cache:
if self.cache.type == "redis":
config_dict["CACHE_TYPE"] = "RedisCache"
config_dict["CACHE_REDIS_URL"] = self.cache.url
else:
config_dict["CACHE_TYPE"] = self.cache.type
config_dict["CACHE_DEFAULT_TIMEOUT"] = self.cache.timeout
# Add CORS settings if present
if self.cors_origins:
config_dict["CORS_ORIGINS"] = self.cors_origins
return config_dict
def get_flask_config():
"""Load and return Flask configuration."""
try:
config = ConfigBase.load(FlaskConfig)
return config
except ConfigFileNotFoundError:
print(f"Configuration file for {ENV} environment not found.")
print("Using default configuration (this is not recommended for production)")
# Default development config
if ENV == "development":
return FlaskConfig(
secret_key="dev-secret-key",
database=DatabaseConfig(url="sqlite:///dev.db"),
admin_emails=["admin@example.com"]
)
# Default testing config
elif ENV == "testing":
return FlaskConfig(
secret_key="test-secret-key",
testing=True,
database=DatabaseConfig(url="sqlite:///:memory:")
)
# For production, do not use defaults
else:
raise
# app.py
from flask import Flask
from config import get_flask_config
# Load configuration
config = get_flask_config()
# Create Flask app
app = Flask(__name__)
app.config.update(config.to_flask_config())
@app.route('/')
def index():
return f"Welcome to {app.config['APP_NAME']}!"
if __name__ == '__main__':
app.run(host=config.host, port=config.port)
Configuration file (_config/flask_app.development.json):
{
"secret_key": "dev-secret-key-123",
"debug": true,
"port": 5000,
"database": {
"url": "sqlite:///dev.db",
"pool_size": 5
},
"cache": {
"type": "redis",
"url": "redis://localhost:6379/0"
},
"cors_origins": [
"http://localhost:3000",
"http://localhost:8080"
],
"app_name": "My Flask App (Dev)",
"admin_emails": [
"admin@example.com",
"dev@example.com"
]
}
Advanced Features
Configuration with immutable (frozen) Dataclasses
This example demonstrates using frozen dataclasses for immutable configuration.
# config.py
from configr import config_class, ConfigBase
from dataclasses import dataclass, field, FrozenInstanceError
from typing import Optional
@dataclass(frozen=True)
class LoggingConfig:
level: str
file: Optional[str] = None
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
@dataclass(frozen=True)
class SecurityConfig:
secret_key: str
token_expiration: int = 3600 # seconds
allowed_hosts: list[str] = field(default_factory=list)
cors_origins: list[str] = field(default_factory=list)
@config_class(file_name="immutable_config.json")
@dataclass(frozen=True)
class AppConfig:
name: str
version: str
debug: bool = False
logging: LoggingConfig = None
security: SecurityConfig = None
feature_flags: dict[str, bool] = field(default_factory=dict)
def is_feature_enabled(self, feature_name: str) -> bool:
"""Check if a feature flag is enabled."""
return self.feature_flags.get(feature_name, False)
# Load the immutable configuration
try:
config = ConfigBase.load(AppConfig)
# Using the configuration
print(f"App: {config.name} v{config.version}")
print(f"Debug mode: {config.debug}")
if config.logging:
print(f"Log level: {config.logging.level}")
if config.security:
print(f"Token expiration: {config.security.token_expiration}s")
# Check feature flags
print("Feature flags:")
for feature, enabled in config.feature_flags.items():
print(f"- {feature}: {'enabled' if enabled else 'disabled'}")
# Attempt to modify (will raise an error)
try:
config.debug = True # This will raise FrozenInstanceError
except FrozenInstanceError as e:
print(f"Cannot modify immutable config: {e}")
except Exception as e:
print(f"Error loading configuration: {e}")
Configuration file (_config/immutable_config.json):
{
"name": "ImmutableApp",
"version": "1.2.0",
"debug": false,
"logging": {
"level": "INFO",
"file": "/var/log/app.log"
},
"security": {
"secret_key": "very-secret-key-123",
"token_expiration": 7200,
"allowed_hosts": [
"example.com",
"api.example.com"
],
"cors_origins": [
"https://app.example.com"
]
},
"feature_flags": {
"new_ui": true,
"advanced_analytics": false,
"experimental_api": false
}
}