Basic Usage
This guide walks through the common usage patterns for Configr in your Python applications.
Project Setup
Let's begin with a simple project structure:
my_project/
├── _config/
│ ├── database.json
│ └── app_settings.yaml
├── app.py
└── requirements.txt
Configuration Files
Here are examples of configuration files:
_config/database.json
{
"host": "localhost",
"port": 5432,
"username": "admin",
"password": "secure_password",
"database": "my_app"
}
_config/app_settings.yaml
debug: true
log_level: DEBUG
max_connections: 50
timeout: 30
enable_caching: true
enable_metrics: false
Defining Configuration Classes
First, define your configuration classes that match your configuration files:
# config.py
from configr import config_class
@config_class(file_name="database.json")
class DatabaseConfig:
username: str
password: str
database: str
host: str
port: int = 5432
@config_class(file_name="app_settings.yaml")
class AppSettings:
debug: bool = False
log_level: str = "INFO"
max_connections: int = 100
timeout: int = 60
enable_caching: bool = False
enable_metrics: bool = False
Loading Configuration
Next, load the configuration in your application:
# app.py
from configr import ConfigBase
from config import DatabaseConfig, AppSettings
# Load configurations
db_config = ConfigBase.load(DatabaseConfig)
app_settings = ConfigBase.load(AppSettings)
# Use configurations
if app_settings.debug:
print(f"Running in DEBUG mode with log level {app_settings.log_level}")
print(f"Caching enabled: {app_settings.enable_caching}")
# Database connection example
print(f"Connecting to database {db_config.database} at {db_config.host}:{db_config.port}")
print(f"Using credentials: {db_config.username}:{'*' * len(db_config.password)}")
Error Handling
Here's how to handle common errors:
from configr import ConfigBase, ConfigFileNotFoundError, ConfigValidationError
try:
config = ConfigBase.load(DatabaseConfig)
except ConfigFileNotFoundError as e:
print(f"Configuration file not found: {e}")
print("Using default configuration...")
config = DatabaseConfig(
host="localhost",
username="default",
password="default",
database="default_db"
)
except ConfigValidationError as e:
print(f"Configuration validation failed: {e}")
raise # Re-raise if validation is critical
except Exception as e:
print(f"Unexpected error: {e}")
raise
Environment-Specific Configuration
For different environments (development, testing, production), you can:
-
Use environment variables to select configuration files:
-
Override values programmatically after loading using environment variables:
-
Use the built-in EnvVarConfigLoader directly:
from configr import config_class, ConfigBase # Don't specify file_name to use environment variables @config_class() class DatabaseConfig: host: str = "localhost" port: int = 5432 username: str = None password: str = None database: str = None # Set environment variables: # export DATABASECONFIG_HOST=prod-db.example.com # export DATABASECONFIG_PORT=5432 # export DATABASECONFIG_USERNAME=admin # etc. # Load from environment variables db_config = ConfigBase.load(DatabaseConfig) -
Use .env files with the built-in DotEnvConfigLoader:
from configr import config_class, ConfigBase @config_class(file_name="database") class DatabaseConfig: host: str = "localhost" port: int = 5432 username: str = None password: str = None database: str = None # Create _config/.env file with: # DATABASE_HOST=prod-db.example.com # DATABASE_PORT=5432 # DATABASE_USERNAME=admin # DATABASE_PASSWORD=secure_password # DATABASE_DATABASE=production_db # Load from .env file (requires python-dotenv) db_config = ConfigBase.load(DatabaseConfig)Note: .env file support requires installing the dotenv extra:
pip install py-configr[dotenv]
Working with Nested Configurations
Configr automatically handles nested dataclass structures in your configuration hierarchy. This allows you to organize complex configuration in a type-safe and well-structured manner.
Defining Nested Configuration Classes
from configr import config_class
from dataclasses import dataclass
@dataclass
class LoggingConfig:
level: str = "INFO"
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
file: str = None
@dataclass
class Tag:
name: str = None
category: str = None
@config_class(file_name="app_config.json")
class AppConfig:
name: str
version: str
debug: bool = False
logging: LoggingConfig = None
tags: list[Tag] = None
Configuration File Structure
The corresponding JSON file structure would look like:
{
"name": "MyApp",
"version": "1.0.0",
"debug": true,
"logging": {
"level": "DEBUG",
"file": "app.log"
},
"tags": [
{
"name": "MyApp",
"category": "application"
},
{
"name": "1.0.0",
"category": "version"
}
]
}
Automatic Conversion
Configr will automatically:
- Detect that
loggingis a field typed asLoggingConfigdataclass - Convert the nested JSON object to a
LoggingConfiginstance - Detect that 'tags' is a field with a list of type
Tagdataclass - Convert each list element JSON object to a
Taginstance - Perform this conversion recursively for any level of nesting
Accessing Nested Configuration
You can access the nested configuration with native dot notation:
config = ConfigBase.load(AppConfig)
# Access nested configuration using dot notation
log_level = config.logging.level
log_file = config.logging.file
tag1 = config.tags[0].name
print(f"Logging to {log_file} with level {log_level}")
for tag in config.tags:
print(f"Tag: {tag.name=}, {tag.category=}")
Default Values in Nested Classes
You can provide default values at any level of the configuration hierarchy:
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = None
password: str = None
@dataclass
class CacheConfig:
enabled: bool = False
ttl: int = 300
@config_class(file_name="server_config.json")
class ServerConfig:
host: str = "0.0.0.0"
port: int = 8080
database: DatabaseConfig = None
cache: CacheConfig = None
With this approach, if the configuration file doesn't specify certain nested objects, they'll be created with their default values if all required fields have defaults.
Configuration Directory
By default, Configr looks for configuration files in the _config/ directory. You can customize this:
from configr import ConfigBase
from pathlib import Path
# Set configuration directory
ConfigBase.set_config_dir("path/to/config")
# Or using a Path object
ConfigBase.set_config_dir(Path("path/to/config"))
# Then load configuration
config = ConfigBase.load(AppConfig)
Putting It All Together
Here's a complete example integrating the concepts above:
# config.py
import os
from configr import config_class, ConfigBase, ConfigFileNotFoundError
from dataclasses import dataclass
# Get environment
ENV = os.environ.get("ENV", "development")
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = None
password: str = None
database: str = None
@dataclass
class LoggingConfig:
level: str = "INFO"
file: str = None
@config_class(file_name=f"app.{ENV}.json")
class AppConfig:
name: str = "MyApp"
version: str = "1.0.0"
debug: bool = False
database: DatabaseConfig = None
logging: LoggingConfig = None
def __post_init__(self):
# Apply environment variable overrides
if "LOG_LEVEL" in os.environ:
self.logging.level = os.environ["LOG_LEVEL"]
Next Steps
Now that you understand the basics of using Configr, you might want to explore:
- Configuration Classes for more details on defining configuration structures
- Custom Loaders to learn how to extend Configr with support for additional file formats