Skip to main content

A flexible configuration manager for Python applications with .env support

Project description

WL Config Manager

A flexible configuration manager for Python applications that supports multiple file formats, environment variable overrides, and dot notation access.

Features

  • Multiple Formats - YAML, JSON, and INI configuration files
  • Dot Notation - Access nested values with config.server.port
  • Environment Variables - Override config values with environment variables
  • Default Values - Built-in defaults with easy overrides
  • File Search - Automatically find config files in standard locations
  • Validation - Ensure required keys are present
  • CLI Tool - Command-line interface for config manipulation
  • Type Conversion - Automatic type conversion for environment variables
  • Live Reload - Reload configuration without restarting

Installation

pip install wl-config-manager

Dependencies

  • wl_version_manager
  • pyyaml>=5.1

Quick Start

Basic Usage

from wl_config_manager import ConfigManager

# Load config from file
config = ConfigManager(config_path="config.yaml")

# Access values with dot notation
port = config.server.port
debug = config.app.debug

# Or use get() method
database_url = config.get("database.url", default="sqlite:///app.db")

Configuration File Example

# config.yaml
app:
  name: "My Application"
  debug: false
  version: "1.0.0"

server:
  host: "0.0.0.0"
  port: 8080
  workers: 4

database:
  url: "postgresql://localhost/myapp"
  pool_size: 10
  echo: false

logging:
  level: "INFO"
  format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

Configuration Loading

Search Paths

If no config path is specified, the manager searches these locations in order:

from wl_config_manager import ConfigManager, DEFAULT_SEARCH_PATHS

# Default search paths on Fedora/CentOS:
# - ~/.config/
# - /etc/
# - /etc/app/
# - current working directory

# Use default search paths
config = ConfigManager(search_paths=DEFAULT_SEARCH_PATHS)

# Or specify custom search paths
config = ConfigManager(search_paths=["/opt/myapp", "/etc/myapp"])

File Formats

The format is auto-detected by file extension, or can be specified:

# Auto-detect format
config = ConfigManager(config_path="settings.json")  # JSON
config = ConfigManager(config_path="app.ini")       # INI
config = ConfigManager(config_path="config.yaml")   # YAML

# Explicitly specify format
config = ConfigManager(config_path="myconfig", format="yaml")

Environment Variable Overrides

Override configuration values using environment variables:

# Configure with env prefix
config = ConfigManager(
    config_path="config.yaml",
    env_prefix="MYAPP_"
)

# Environment variables override config file values
# MYAPP_SERVER__PORT=9000 overrides server.port
# MYAPP_APP__DEBUG=true overrides app.debug
# MYAPP_DATABASE__URL=postgres://prod-server/db overrides database.url

Environment variable rules:

  • Prefix + double underscore (__) for nested values
  • Values are automatically converted to appropriate types (bool, int, float, string)
  • Case-insensitive (environment vars are converted to lowercase)

Default Configuration

Provide default values that are used if not found in config file or environment:

default_config = {
    "app": {
        "name": "DefaultApp",
        "debug": False
    },
    "server": {
        "port": 8080,
        "host": "localhost"
    }
}

config = ConfigManager(
    config_path="config.yaml",
    default_config=default_config
)

Validation

Ensure required configuration keys are present:

# Validate required keys
config = ConfigManager(
    config_path="config.yaml",
    required_keys=["app.name", "server.port", "database.url"]
)
# Raises ConfigValidationError if any required keys are missing

Accessing Configuration Values

Dot Notation Access

# Direct attribute access
app_name = config.app.name
port = config.server.port

# Nested access
log_level = config.logging.level

# Safe access with get()
smtp_host = config.get("email.smtp.host", default="localhost")

# Get entire sections
server_config = config.server  # Returns namespace object
server_dict = config.get("server")  # Returns dict

Dictionary-like Access

# The configuration objects support dict-like operations
if "debug" in config.app:
    print(f"Debug mode: {config.app.debug}")

# Iterate over sections
for section, values in config:
    print(f"{section}: {values}")

# Get items from a section
for key, value in config.items("server"):
    print(f"{key} = {value}")

Modifying Configuration

Set Values

# Set single value
config.set("app.version", "2.0.0")
config.set("server.port", 9000)

# Create nested structures
config.set("new_feature.enabled", True)
config.set("new_feature.options.timeout", 30)

# Update multiple values
config.update({
    "version": "2.0.0",
    "debug": True
})

# Update with prefix
config.update({"port": 9000, "workers": 8}, prefix="server")

Save Configuration

# Save to original file
config.save()

# Save to different file
config.save("new_config.yaml")

# Save in different format
config.save("config.json")  # Auto-converts to JSON

Advanced Usage

Reload Configuration

# Reload from source file (picks up external changes)
config.reload()

Create from Dictionary

config_dict = {
    "app": {"name": "MyApp"},
    "server": {"port": 8080}
}

config = ConfigManager.from_dict(config_dict)

Create from Environment Only

# Load configuration entirely from environment variables
config = ConfigManager.from_env("MYAPP_")

Logging Configuration

# Set logging level for config manager
config = ConfigManager(
    config_path="config.yaml",
    log_level=logging.DEBUG  # See config loading details
)

# Set up file logging
from wl_config_manager import setup_file_logging

setup_file_logging(
    log_dir="/var/log",
    app_name="myapp",
    log_level=logging.INFO
)

Command Line Interface

The package includes a CLI tool for managing configurations:

View Configuration

# Get entire config
wl_config_manager get config.yaml

# Get specific value
wl_config_manager get config.yaml server.port

# Get with default
wl_config_manager get config.yaml app.missing --default="not found"

# Output as JSON
wl_config_manager get --format=json config.yaml

Modify Configuration

# Set values
wl_config_manager set config.yaml app.debug true
wl_config_manager set config.yaml server.port 9000

# Create new config file
wl_config_manager create --format=yaml new_config.yaml

# Create from template
wl_config_manager create --template=default.yaml --vars='{"app":{"name":"MyApp"}}' config.yaml

Validate Configuration

# Check required keys
wl_config_manager validate --required=app.name,server.port config.yaml

Convert Formats

# Convert between formats
wl_config_manager convert config.ini config.yaml
wl_config_manager convert settings.yaml settings.json

List Configuration

# List all values
wl_config_manager list config.yaml

# List section only
wl_config_manager list config.yaml --section=server

Environment Variables

# Show config from environment variables
wl_config_manager env MYAPP_ --format=json

Error Handling

The module provides specific exceptions for different error types:

from wl_config_manager import (
    ConfigError,           # Base exception
    ConfigFileError,       # File not found/readable
    ConfigFormatError,     # Invalid file format
    ConfigValidationError  # Validation failures
)

try:
    config = ConfigManager(config_path="config.yaml")
except ConfigFileError as e:
    print(f"Config file error: {e}")
    print(f"File path: {e.file_path}")
except ConfigValidationError as e:
    print(f"Validation error: {e}")
    print(f"Missing keys: {e.get_missing_keys()}")

Best Practices

  1. Use Environment Variables for Secrets

    export MYAPP_DATABASE__PASSWORD="secret"
    export MYAPP_API__KEY="secret-key"
    
  2. Provide Sensible Defaults

    default_config = {
        "server": {"port": 8080, "host": "0.0.0.0"},
        "logging": {"level": "INFO"}
    }
    
  3. Validate Critical Configuration

    required_keys = ["database.url", "app.secret_key"]
    config = ConfigManager(required_keys=required_keys)
    
  4. Use Type Hints with Config Objects

    from typing import TYPE_CHECKING
    if TYPE_CHECKING:
        from types import SimpleNamespace
    
    def setup_server(server_config: SimpleNamespace):
        port = server_config.port
        host = server_config.host
    

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wl_config_manager-0.1.33.tar.gz (26.2 kB view details)

Uploaded Source

File details

Details for the file wl_config_manager-0.1.33.tar.gz.

File metadata

  • Download URL: wl_config_manager-0.1.33.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for wl_config_manager-0.1.33.tar.gz
Algorithm Hash digest
SHA256 eb8ce6d538da28d7d43ea49f7d8ef6b160b82789c189ee5e0dc52b40498c0b65
MD5 b64f72deba002e50390865e7ae860ed1
BLAKE2b-256 95028c700c08334c16b73bfbd5e657c8709371d0b320deaa76ca842ad4f45f9b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page