Painless YAML configuration library for Python applications with validation, type checking, and multi-source data merging
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive template system for validating and converting configuration values with built-in templates for common types and support for custom validation logic. Templates provide type safety and automatic conversion between YAML representations and Python objects.
The foundation template class that all validation templates inherit from, providing the core validation and conversion interface.
class Template:
def __init__(self, default=REQUIRED):
"""
Create a template with optional default value.
Parameters:
- default: Default value to return when configuration value is missing.
Use REQUIRED (default) to raise exception for missing values.
"""
def value(self, view, template=None):
"""
Get validated value from a ConfigView.
Parameters:
- view (ConfigView): Configuration view to validate
- template (Template, optional): Parent template context
Returns:
Validated and converted value
Raises:
- NotFoundError: If value is missing and required
- ConfigValueError: If value is invalid
"""
def convert(self, value, view):
"""
Convert raw configuration value to target type.
Parameters:
- value: Raw value from configuration source
- view (ConfigView): Configuration view context
Returns:
Converted value
"""
def get_default_value(self, key_name='default'):
"""
Get the default value for this template.
Returns:
Default value or raises NotFoundError if required
"""Templates for primitive types with automatic conversion and validation.
class Integer(Template):
"""Template for integer values with float rounding."""
class Number(Template):
"""Template for numeric values (int or float)."""
class String(Template):
def __init__(self, default=REQUIRED, pattern=None, expand_vars=False):
"""
Template for string values with optional pattern matching and variable expansion.
Parameters:
- default: Default string value
- pattern (str, optional): Regular expression pattern to validate against
- expand_vars (bool): Whether to expand environment variables ($VAR, ${VAR})
"""
class TypeTemplate(Template):
def __init__(self, typ, default=REQUIRED):
"""
Template for validating Python type instances.
Parameters:
- typ (type): Python type to validate against
- default: Default value of the specified type
"""Templates for validating values against predefined choices or multiple possible templates.
class Choice(Template):
def __init__(self, choices, default=REQUIRED):
"""
Template for values from predefined choices.
Parameters:
- choices: Sequence of valid values, dict mapping, or Enum class
- default: Default choice value
"""
class OneOf(Template):
def __init__(self, allowed, default=REQUIRED):
"""
Template accepting values matching any of multiple sub-templates.
Parameters:
- allowed: List of Template objects or convertible values
- default: Default value
"""
class Optional(Template):
def __init__(self, subtemplate, default=None, allow_missing=True):
"""
Template making sub-templates optional with null/missing value handling.
Parameters:
- subtemplate: Template to apply when value is present and not null
- default: Value to return for null/missing values
- allow_missing (bool): Whether to allow missing values
"""Templates for validating collections and nested data structures.
class MappingTemplate(Template):
def __init__(self, mapping):
"""
Template for dictionary validation with typed values.
Parameters:
- mapping (dict): Dictionary specifying value templates for each key
"""
class Sequence(Template):
def __init__(self, subtemplate):
"""
Template for list validation with uniform item types.
Parameters:
- subtemplate: Template to apply to each list item
"""
class MappingValues(Template):
def __init__(self, subtemplate):
"""
Template for mappings with variable keys but typed values.
Parameters:
- subtemplate: Template to apply to all mapping values
"""
class StrSeq(Template):
def __init__(self, split=True, default=REQUIRED):
"""
Template for lists of strings with optional whitespace splitting.
Parameters:
- split (bool): Whether to split single strings on whitespace
- default: Default list value
"""
class Pairs(StrSeq):
def __init__(self, default_value=None):
"""
Template for ordered key-value pairs from various input formats.
Parameters:
- default_value: Default value for keys without explicit values
"""Templates for validating and resolving file paths with platform-aware handling.
class Filename(Template):
def __init__(self, default=REQUIRED, cwd=None, relative_to=None,
in_app_dir=False, in_source_dir=False):
"""
Template for filename validation with path resolution.
Parameters:
- default: Default filename
- cwd (str, optional): Working directory for relative paths
- relative_to (str, optional): Key name for sibling path resolution
- in_app_dir (bool): Resolve relative to application config directory
- in_source_dir (bool): Resolve relative to configuration file directory
"""
class Path(Filename):
"""Template for pathlib.Path objects with same resolution as Filename."""Helper functions and classes for working with templates and validation.
def as_template(value):
"""
Convert shorthand Python values to Template objects.
Parameters:
- value: Python type, value, or existing Template
Returns:
Template: Corresponding Template object
"""
class AttrDict(dict):
"""
Dictionary subclass with attribute-style access.
Enables dot notation: config.database.host instead of config['database']['host']
"""
def __getattr__(self, key): ...
def __setattr__(self, key, value): ...import confuse
config = confuse.Configuration('myapp')
# Simple type templates
port = config['port'].get(confuse.Integer())
timeout = config['timeout'].get(confuse.Number(30.0)) # Default: 30.0
name = config['app_name'].get(confuse.String())
# Type shorthand (automatically converted to templates)
port = config['port'].get(int) # Same as Integer()
timeout = config['timeout'].get(30.0) # Same as Number(30.0)
name = config['app_name'].get(str) # Same as String()import confuse
config = confuse.Configuration('myapp')
# String with regex pattern validation
email_template = confuse.String(pattern=r'^[^@]+@[^@]+\.[^@]+$')
email = config['admin_email'].get(email_template)
# String with environment variable expansion
path_template = confuse.String(expand_vars=True)
data_path = config['data_path'].get(path_template) # Expands $HOME/dataimport confuse
from enum import Enum
config = confuse.Configuration('myapp')
# Choice from list
log_level = config['log_level'].get(confuse.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR']))
# Choice from dict (maps keys to values)
env_mapping = {'dev': 'development', 'prod': 'production'}
environment = config['env'].get(confuse.Choice(env_mapping))
# Choice from Enum
class LogLevel(Enum):
DEBUG = 'debug'
INFO = 'info'
WARNING = 'warning'
ERROR = 'error'
log_level = config['log_level'].get(confuse.Choice(LogLevel))import confuse
config = confuse.Configuration('myapp')
# Dictionary template with typed values
database_template = {
'host': str,
'port': confuse.Integer(5432),
'username': str,
'password': str,
'ssl': bool,
'timeout': confuse.Number(30.0),
'options': confuse.StrSeq(),
}
# Get validated configuration as AttrDict
db_config = config['database'].get(database_template)
print(f"Connecting to {db_config.host}:{db_config.port}")
print(f"SSL enabled: {db_config.ssl}")
print(f"Options: {db_config.options}")
# List of dictionaries
server_template = confuse.Sequence({
'hostname': str,
'port': confuse.Integer(),
'enabled': bool,
'tags': confuse.StrSeq(),
})
servers = config['servers'].get(server_template)
for server in servers:
print(f"Server: {server.hostname}:{server.port} (enabled: {server.enabled})")import confuse
config = confuse.Configuration('myapp')
# Basic filename template
config_file = config['config_file'].get(confuse.Filename())
# Filename relative to application config directory
log_file = config['log_file'].get(confuse.Filename(in_app_dir=True))
# Filename relative to configuration source file
data_file = config['data_file'].get(confuse.Filename(in_source_dir=True))
# Filename with custom working directory
output_file = config['output'].get(confuse.Filename(cwd='/tmp'))
# Filename relative to another configuration value
template = {
'base_dir': confuse.Filename(),
'data_file': confuse.Filename(relative_to='base_dir'),
}
paths = config.get(template)
print(f"Data file: {paths.data_file}") # Resolved relative to base_dirimport confuse
config = confuse.Configuration('myapp')
# Optional template with default
debug_mode = config['debug'].get(confuse.Optional(bool, default=False))
# Template with REQUIRED (raises exception if missing)
api_key = config['api_key'].get(confuse.String()) # Must be present
# Template with default value
max_workers = config['max_workers'].get(confuse.Integer(4))
# Optional nested configuration
cache_template = confuse.Optional({
'enabled': bool,
'ttl': confuse.Integer(3600),
'backend': confuse.Choice(['memory', 'redis']),
})
cache_config = config['cache'].get(cache_template)
if cache_config:
print(f"Cache enabled: {cache_config.enabled}")import confuse
config = confuse.Configuration('myapp')
# Value can be boolean or string
import_mode = config['import_mode'].get(confuse.OneOf([bool, 'ask', 'skip']))
# Value can be filename or URL
source_template = confuse.OneOf([
confuse.Filename(),
confuse.String(pattern=r'^https?://'),
])
data_source = config['data_source'].get(source_template)
# Multiple validation options with different defaults
retry_template = confuse.OneOf([
confuse.Integer(), # Number of retries
bool, # True/False for default retry count
confuse.String(pattern=r'^(always|never)$'), # Special string values
])
retry_config = config['retry'].get(retry_template)import confuse
config = confuse.Configuration('myapp')
# List of strings (splits single string on whitespace)
plugins = config['plugins'].get(confuse.StrSeq())
# YAML: plugins: "plugin1 plugin2 plugin3" -> ['plugin1', 'plugin2', 'plugin3']
# YAML: plugins: [plugin1, plugin2, plugin3] -> ['plugin1', 'plugin2', 'plugin3']
# List of strings without splitting
tags = config['tags'].get(confuse.StrSeq(split=False))
# YAML: tags: "development staging" -> ['development staging']
# Key-value pairs
env_vars = config['environment'].get(confuse.Pairs())
# YAML:
# environment:
# - DATABASE_URL: postgres://localhost/db
# - [API_KEY, secret123]
# - DEBUG # Uses default_value
# Result: [('DATABASE_URL', 'postgres://localhost/db'), ('API_KEY', 'secret123'), ('DEBUG', None)]
# Pairs with default value
flags = config['flags'].get(confuse.Pairs(default_value=True))
# Result: [('DEBUG', True)] for YAML: flags: [DEBUG]import confuse
class EmailTemplate(confuse.Template):
def convert(self, value, view):
if not isinstance(value, str):
self.fail('must be a string', view, True)
if '@' not in value:
self.fail('must be a valid email address', view)
return value.lower() # Normalize to lowercase
config = confuse.Configuration('myapp')
admin_email = config['admin_email'].get(EmailTemplate())Install with Tessl CLI
npx tessl i tessl/pypi-confuse