CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-confuse

Painless YAML configuration library for Python applications with validation, type checking, and multi-source data merging

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

sources.mddocs/

Sources and Integration

Support for multiple configuration data sources including YAML files, environment variables, and command-line arguments with automatic type conversion and priority handling. Configuration sources enable flexible data input from various locations with seamless merging and override capabilities.

Capabilities

Configuration Sources

Base classes for different types of configuration data sources that can be layered together with priority ordering.

class ConfigSource(dict):
    def __init__(self, value, filename=None, default=False, base_for_paths=False):
        """
        Create a configuration source from a dictionary.
        
        Parameters:
        - value (dict): Configuration data dictionary
        - filename (str, optional): Source file path for this configuration data
        - default (bool): Whether this source provides application defaults
        - base_for_paths (bool): Use source file's directory for relative path resolution
        """
    
    @classmethod
    def of(cls, value):
        """
        Create ConfigSource from dictionary or existing ConfigSource.
        
        Parameters:
        - value: Dictionary or ConfigSource object
        
        Returns:
        ConfigSource: New or existing ConfigSource object
        """

YAML File Sources

Configuration sources that read from YAML files with customizable loading and error handling.

class YamlSource(ConfigSource):
    def __init__(self, filename=None, default=False, base_for_paths=False, 
                 optional=False, loader=yaml_util.Loader):
        """
        Create YAML configuration source by reading from file.
        
        Parameters:
        - filename (str): Path to YAML configuration file
        - default (bool): Whether this is a default configuration source
        - base_for_paths (bool): Use file's directory as base for relative paths
        - optional (bool): Don't raise error if file doesn't exist
        - loader: PyYAML Loader class for parsing YAML
        
        Raises:
        - ConfigReadError: If file cannot be read (unless optional=True)
        """
    
    def load(self):
        """
        Load YAML data from the source's filename.
        
        Raises:
        - ConfigReadError: If file cannot be read or parsed
        """

Environment Variable Sources

Configuration sources that read from environment variables with flexible naming and type conversion.

class EnvSource(ConfigSource):
    def __init__(self, prefix, sep='__', lower=True, handle_lists=True,
                 parse_yaml_docs=False, loader=yaml_util.Loader):
        """
        Create configuration source from environment variables.
        
        Parameters:
        - prefix (str): Environment variable prefix for identification
        - sep (str): Separator within variable names for nested keys
        - lower (bool): Convert variable names to lowercase after prefix matching
        - handle_lists (bool): Convert sequential integer keys to lists
        - parse_yaml_docs (bool): Parse values as full YAML documents vs scalars
        - loader: PyYAML Loader class for parsing values
        """
    
    def load(self):
        """
        Load configuration data from environment variables.
        """
    
    @classmethod
    def _convert_dict_lists(cls, obj):
        """
        Convert dicts with sequential integer keys (0, 1, 2...) to lists.
        
        Parameters:
        - obj: Dictionary potentially containing list-like structures
        
        Returns:
        Converted object with dicts converted to lists where appropriate
        """

YAML Processing Utilities

Custom YAML loading and dumping classes with enhanced features for configuration management.

class Loader(yaml.SafeLoader):
    """
    Custom YAML loader with Unicode strings and OrderedDict support.
    Features:
    - All strings as Unicode objects
    - All maps as OrderedDicts  
    - Strings can begin with % without quotation
    """
    
    @staticmethod
    def add_constructors(loader):
        """
        Add custom constructors to a PyYAML Loader class.
        
        Parameters:
        - loader: PyYAML Loader class to modify
        """

class Dumper(yaml.SafeDumper):
    """
    Custom YAML dumper with OrderedDict and formatting enhancements.
    Features:
    - OrderedDicts as ordinary mappings
    - Short lists in inline style
    - Boolean values as 'yes'/'no'
    - None values as empty strings
    """

def load_yaml(filename, loader=Loader):
    """
    Read YAML document from file with error handling.
    
    Parameters:
    - filename (str): Path to YAML file
    - loader: PyYAML Loader class
    
    Returns:
    Parsed YAML data
    
    Raises:
    - ConfigReadError: If file cannot be read or parsed
    """

def load_yaml_string(yaml_string, name, loader=Loader):
    """
    Parse YAML document from string with error handling.
    
    Parameters:
    - yaml_string (str): YAML content as string
    - name (str): Name for error messages
    - loader: PyYAML Loader class
    
    Returns:
    Parsed YAML data
    
    Raises:
    - ConfigReadError: If string cannot be parsed
    """

def parse_as_scalar(value, loader=Loader):
    """
    Parse value as YAML scalar for consistent type conversion.
    
    Parameters:
    - value (str): String value to parse
    - loader: PyYAML Loader class
    
    Returns:
    Type-converted value (int, float, bool, None, or original string)
    """

def restore_yaml_comments(data, default_data):
    """
    Restore comments from default YAML to generated data.
    
    Parameters:
    - data (str): Generated YAML data string
    - default_data (str): Default YAML data with comments
    
    Returns:
    str: YAML data with comments restored from default_data
    """

Platform Utilities

Cross-platform utilities for configuration directory discovery and path handling.

def config_dirs():
    """
    Get platform-specific configuration directory candidates.
    
    Returns:
    list: Configuration directory paths in priority order
    """

def xdg_config_dirs():
    """
    Get list of paths from XDG_CONFIG_DIRS and XDG_CONFIG_HOME environment variables.
    
    Returns:
    list: XDG configuration directory paths
    """

def iter_first(sequence):
    """
    Get the first element from an iterable or raise ValueError if empty.
    
    Parameters:
    - sequence: Iterable to get first element from
    
    Returns:
    First element from sequence
    
    Raises:
    ValueError: If sequence is empty
    """

def namespace_to_dict(obj):
    """
    Convert argparse.Namespace or optparse.Values to dict representation.
    
    Parameters:
    - obj: Namespace or Values object to convert
    
    Returns:
    dict: Dictionary representation or original object if not convertible
    """

def find_package_path(name):
    """
    Find the filesystem path for a Python package.
    
    Parameters:
    - name (str): Package name
    
    Returns:
    str or None: Package directory path, or None if not found
    """

def build_dict(obj, sep='', keep_none=False):
    """
    Recursively build nested dictionary from namespace/dict with key splitting.
    
    Parameters:
    - obj: Namespace, Values, dict, or other object to convert
    - sep (str): Separator for splitting keys into nested structure
    - keep_none (bool): Whether to keep keys with None values
    
    Returns:
    dict: Nested dictionary structure
    """

Platform Constants

UNIX_DIR_FALLBACK = '~/.config'  # Default Unix configuration directory
WINDOWS_DIR_VAR = 'APPDATA'      # Windows environment variable name
WINDOWS_DIR_FALLBACK = '~\\AppData\\Roaming'  # Windows config directory fallback
MAC_DIR = '~/Library/Application Support'     # macOS configuration directory

Usage Examples

YAML File Sources

import confuse

config = confuse.Configuration('myapp', read=False)

# Add specific YAML file
config.set_file('/etc/myapp/config.yaml')

# Add optional YAML file (no error if missing)
yaml_source = confuse.YamlSource('/home/user/myapp.yaml', optional=True)
config.add(yaml_source)

# YAML file with path resolution relative to file location
config_source = confuse.YamlSource('config.yaml', base_for_paths=True)
config.add(config_source)

Environment Variable Sources

import confuse
import os

config = confuse.Configuration('myapp', read=False)

# Basic environment source with MYAPP_ prefix
config.set_env('MYAPP_')

# Environment variables:
# MYAPP_DATABASE_HOST=localhost -> config['database']['host'] = 'localhost'
# MYAPP_DATABASE_PORT=5432 -> config['database']['port'] = 5432

# Custom environment source with different settings
env_source = confuse.EnvSource(
    prefix='MYAPP_',
    sep='__',           # Use __ as separator instead of _
    lower=True,         # Convert to lowercase
    handle_lists=True,  # Convert sequential keys to lists
    parse_yaml_docs=False  # Parse as scalars only
)
config.add(env_source)

# Example environment variables:
# MYAPP_SERVERS__0__HOST=server1.example.com
# MYAPP_SERVERS__0__PORT=8080
# MYAPP_SERVERS__1__HOST=server2.example.com  
# MYAPP_SERVERS__1__PORT=8081
# Results in: config['servers'] = [{'host': 'server1.example.com', 'port': 8080}, ...]

Environment Variable List Handling

import confuse
import os

# Set up environment variables for list conversion
os.environ['MYAPP_ITEMS__0'] = 'first'
os.environ['MYAPP_ITEMS__1'] = 'second'
os.environ['MYAPP_ITEMS__2'] = 'third'

config = confuse.Configuration('myapp', read=False)
config.set_env('MYAPP_')

# Automatically converted to list
items = config['items'].get(list)  # ['first', 'second', 'third']

YAML Document Parsing from Environment

import confuse
import os

# Environment variable with YAML content
os.environ['MYAPP_COMPLEX'] = '''
database:
  host: localhost
  port: 5432
features: [auth, logging, metrics]
'''

config = confuse.Configuration('myapp', read=False)

# Parse environment values as full YAML documents
env_source = confuse.EnvSource('MYAPP_', parse_yaml_docs=True)
config.add(env_source)

# Access parsed YAML structure
db_host = config['complex']['database']['host'].as_str()  # 'localhost'
features = config['complex']['features'].as_str_seq()     # ['auth', 'logging', 'metrics']

Command-Line Integration

import confuse
import argparse

config = confuse.Configuration('myapp')

# Set up argument parser
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost')
parser.add_argument('--port', type=int, default=8080)
parser.add_argument('--database-url', dest='database.url')
parser.add_argument('--enable-feature', action='append', dest='features')
parser.add_argument('--verbose', action='store_true')

args = parser.parse_args()

# Overlay arguments onto configuration
config.set_args(args, dots=True)

# Command line overrides configuration files
host = config['host'].as_str()              # From --host or config file
port = config['port'].as_number()           # From --port or config file  
db_url = config['database']['url'].as_str() # From --database-url
features = config['features'].as_str_seq()  # From --enable-feature (multiple)

Custom YAML Loading

import confuse
import yaml

# Create custom loader with additional constructors
class CustomLoader(confuse.yaml_util.Loader):
    pass

def construct_include(loader, node):
    """Custom constructor for !include directive"""
    filename = loader.construct_scalar(node)
    with open(filename, 'r') as f:
        return yaml.load(f, Loader=CustomLoader)

CustomLoader.add_constructor('!include', construct_include)

# Use custom loader
config = confuse.Configuration('myapp', read=False, loader=CustomLoader)
config.set_file('config.yaml')  # Can now use !include directive

Source Priority and Layering

import confuse

config = confuse.Configuration('myapp', read=False)

# Add sources in reverse priority order (lowest to highest)
config.add({'timeout': 30, 'debug': False})              # 1. Defaults (lowest)
config.set_file('/etc/myapp/system.yaml')                # 2. System config
config.read(user=True, defaults=False)                   # 3. User config  
config.set_env('MYAPP_')                                  # 4. Environment variables
# Command-line arguments would be added with set_args()  # 5. CLI args (highest)

# Runtime overrides (highest priority)
config.set({'debug': True})                              # 6. Runtime (highest)

# Values are resolved in priority order
debug_mode = config['debug'].get(bool)  # True (from runtime override)
timeout = config['timeout'].get(int)    # 30 (from defaults, unless overridden)

Platform-Specific Configuration

import confuse

config = confuse.Configuration('myapp')

# Configuration files searched in platform-specific locations:
# Linux: ~/.config/myapp/config.yaml, /etc/xdg/myapp/config.yaml, etc.
# macOS: ~/Library/Application Support/myapp/config.yaml, ~/.config/myapp/config.yaml, etc.  
# Windows: %APPDATA%\\myapp\\config.yaml

print(f"Config directory: {config.config_dir()}")
print(f"User config file: {config.user_config_path()}")

# Get all platform-specific directories
config_directories = confuse.config_dirs()
print("Config search paths:", config_directories)

Error Handling

import confuse

try:
    # This may raise ConfigReadError if file is malformed
    yaml_source = confuse.YamlSource('bad_config.yaml')
except confuse.ConfigReadError as e:
    print(f"Configuration file error: {e}")

try:
    # Load YAML string with error handling
    config_data = confuse.load_yaml_string('''
    invalid: [unclosed list
    ''', 'config string')
except confuse.ConfigReadError as e:
    print(f"YAML parsing error: {e}")

# Optional file source (no error if missing)
optional_source = confuse.YamlSource('optional_config.yaml', optional=True)
config.add(optional_source)

Configuration Dumping

import confuse

config = confuse.Configuration('myapp')
config.set({'database': {'host': 'localhost', 'port': 5432}})

# Generate YAML representation
yaml_output = config.dump(full=True, redact=False)
print("Full configuration:")
print(yaml_output)

# Generate with sensitive values redacted
safe_output = config.dump(full=True, redact=True)
print("Safe configuration:")
print(safe_output)

# Custom YAML dumping with Confuse's Dumper
import confuse.yaml_util

data = {'servers': ['web1', 'web2'], 'enabled': True}
yaml_str = confuse.yaml_util.yaml.dump(data, Dumper=confuse.yaml_util.Dumper)
print(yaml_str)  # Uses Confuse's formatting preferences

Install with Tessl CLI

npx tessl i tessl/pypi-confuse

docs

configuration.md

index.md

sources.md

templates.md

views.md

tile.json