Skip to main content

A Python package for managing YAML configuration files while preserving structure, comments, and formatting with attribute and dictionary access

Project description

Yamleaf

A Python package for managing YAML configuration files while preserving structure, comments, and formatting. Supports both dictionary and attribute access patterns with optional type validation.

Features

  • 🎯 Preserves YAML Structure: Comments, formatting, and key order maintained
  • 🔧 Multiple Access Patterns: Dictionary, attribute, and dot notation access
  • 🛡️ Type Safety: Optional type validation with dataclass integration
  • 📁 Auto-save: Automatic saving on application exit
  • 🔄 Backup Support: Automatic backup creation before saves
  • 🏗️ Create Missing Files: Optionally create config files with defaults
  • 🔍 IDE Support: Full autocomplete and type hints
  • 📋 List Support: Native support for list operations
  • 🎨 Preserves Comments: Your YAML comments stay intact

Installation

pip install yamleaf

Quick Start

Basic Usage

from yamleaf import ConfigManager

# Load existing config with auto-save
config = ConfigManager('config.yaml', auto_save=True)

# Dictionary-style access (familiar)
db_host = config['database.host']
config['database.port'] = 5432

# Attribute-style access (modern)
api_key = config.api.key
config.api.timeout = 30

# Mixed access patterns
config.logging.level = 'DEBUG'
config['features.new_ui'] = True

With Type Validation

from dataclasses import dataclass
from yamleaf import ConfigManager

@dataclass
class DatabaseConfig:
    host: str
    port: int
    ssl: bool

@dataclass
class AppConfig:
    name: str
    debug: bool
    database: DatabaseConfig

# Type-validated configuration
config = ConfigManager(
    'config.yaml',
    config_class=AppConfig,
    validate_types=True
)

# Automatic type conversion
config.database.port = "5432"  # Converted to int
config.debug = "false"         # Converted to bool

Access Patterns

The package supports multiple ways to access your configuration:

1. Dictionary Access

# Get values
host = config['database']['host']
port = config['database.port']  # Dot notation

# Set values
config['database']['host'] = 'new-host'
config['api.timeout'] = 60

2. Attribute Access

# Get values
host = config.database.host
timeout = config.api.timeout

# Set values
config.database.host = 'new-host'
config.api.timeout = 60

3. Method Access

# Get with defaults
host = config.get('database.host', 'localhost')

# Set values
config.set('database.host', 'new-host')

# Bulk updates
config.update({
    'database.host': 'new-host',
    'api.timeout': 60
})

Working with Lists

# Access list items
first_service = config.services[0]
service_name = config.services[0].name

# Modify lists
config.services[0].port = 8080
config.services.append({
    'name': 'new_service',
    'port': 9000
})

Advanced Features

Creating Missing Configs

default_config = {
    'app': {'name': 'MyApp', 'version': '1.0.0'},
    'database': {'host': 'localhost', 'port': 5432}
}

config = ConfigManager(
    'new_config.yaml',
    create_if_missing=True,
    default_config=default_config
)

Backup and Context Manager

# With automatic backups
config = ConfigManager('config.yaml', backup=True)

# Context manager (auto-saves on exit)
with ConfigManager('config.yaml') as config:
    config.database.host = 'new-host'
    # Automatically saved when exiting context

Type Validation

@dataclass
class MyConfig:
    name: str
    port: int
    debug: bool

config = ConfigManager(
    'config.yaml',
    config_class=MyConfig,
    validate_types=True
)

# Type errors are caught and converted when possible
config.port = "8080"  # Automatically converted to int

Migration from PyYAML

The package is designed as a drop-in replacement for PyYAML workflows:

Before (PyYAML)

import yaml

with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)

db_host = config['database']['host']
config['database']['port'] = 5432

with open('config.yaml', 'w') as f:
    yaml.dump(config, f)  # Loses formatting!

After (yamleaf)

from yamleaf import ConfigManager

config = ConfigManager('config.yaml', auto_save=True)

# All your existing code still works
db_host = config['database']['host']
config['database']['port'] = 5432

# Plus new capabilities
db_host = config.database.host  # Attribute access
config.database.port = 5432    # Attribute setting

# Formatting preserved automatically!

Configuration Examples

Simple Web App

app:
  name: "MyWebApp"
  port: 8080
  debug: false

database:
  host: "localhost"
  port: 5432
  name: "myapp"

api:
  timeout: 30
  rate_limit: 100

Microservices

services:
  - name: "auth_service"
    port: 8001
    endpoints: ["/login", "/logout"]

  - name: "api_service"
    port: 8002
    endpoints: ["/api/v1", "/api/v2"]

monitoring:
  enabled: true
  metrics_port: 9090

Real-World Examples

Backend Integration Service

from yamleaf import ConfigManager

# Perfect for your Python backend integrations
config = ConfigManager('integration_config.yaml', auto_save=True)

# API configurations for different services
config.services.stripe.api_key = "sk_live_..."
config.services.stripe.webhook_secret = "whsec_..."
config.services.sendgrid.api_key = "SG...."

# Database connections
config.databases.primary.host = "prod-db.company.com"
config.databases.analytics.host = "analytics-db.company.com"

# AI agent configurations
config.ai_agents.chatbot.model = "gpt-4"
config.ai_agents.chatbot.temperature = 0.7
config.ai_agents.classifier.confidence_threshold = 0.85

# Bot configurations
config.bots.slack_bot.token = "xoxb-..."
config.bots.discord_bot.enabled = True
config.bots.telegram_bot.polling_interval = 30

Automated Bot Configuration

# Load bot configuration with type validation
@dataclass
class BotConfig:
    name: str
    token: str
    channels: list
    enabled: bool
    rate_limit: int

config = ConfigManager(
    'bot_config.yaml',
    config_class=BotConfig,
    validate_types=True
)

# Easy bot management
for bot_name in config.bots.keys():
    bot = config.bots[bot_name]
    if bot.enabled:
        print(f"Starting {bot.name} bot on {len(bot.channels)} channels")

# Runtime configuration updates
config.bots.monitoring_bot.rate_limit = 100
config.bots.news_bot.channels.append("#tech-news")

API Reference

ConfigManager

Constructor

ConfigManager(
    config_path: Union[str, Path],
    config_class: Optional[type] = None,
    auto_save: bool = True,
    backup: bool = False,
    indent: int = 2,
    sequence_indent: int = 4,
    validate_types: bool = True,
    create_if_missing: bool = False,
    default_config: Optional[Dict[str, Any]] = None
)

Parameters:

  • config_path: Path to the YAML configuration file
  • config_class: Optional dataclass for type validation
  • auto_save: Automatically save changes on exit
  • backup: Create backup before saving
  • indent: YAML indentation for maps
  • sequence_indent: YAML indentation for sequences
  • validate_types: Enable type validation
  • create_if_missing: Create config file if it doesn't exist
  • default_config: Default configuration for new files

Methods

  • get(key_path: str, default: Any = None) -> Any: Get value using dot notation
  • set(key_path: str, value: Any) -> None: Set value using dot notation
  • update(updates: Dict[str, Any]) -> None: Update multiple values
  • delete(key_path: str) -> None: Delete a configuration key
  • save(path: Optional[Union[str, Path]] = None) -> None: Save configuration
  • reload() -> None: Reload from file, discarding changes
  • reset() -> None: Reset to default configuration

Properties

  • config: ConfigProxy: Attribute access proxy
  • is_modified: bool: Check if configuration has been modified
  • raw_config: Dict[str, Any]: Raw configuration dictionary

ConfigProxy

The ConfigProxy class provides attribute-style access to configuration values:

# Access nested values
config.database.host
config.api.endpoints[0]

# Set nested values
config.database.host = "new-host"
config.features.new_ui = True

# Convert back to dictionary
config.database.to_dict()

Error Handling

The package provides specific exceptions for different error conditions:

from yamleaf import (
    ConfigManager, 
    ConfigError, 
    ConfigNotFoundError, 
    ConfigValidationError
)

try:
    config = ConfigManager('config.yaml')
except ConfigNotFoundError:
    print("Config file not found, creating default...")
    config = ConfigManager(
        'config.yaml',
        create_if_missing=True,
        default_config={'app': {'name': 'MyApp'}}
    )
except ConfigValidationError as e:
    print(f"Config validation failed: {e}")
except ConfigError as e:
    print(f"Config error: {e}")

Best Practices

1. Use Type Validation for Production

@dataclass
class ProductionConfig:
    database_url: str
    api_key: str
    debug: bool = False

config = ConfigManager(
    'prod_config.yaml',
    config_class=ProductionConfig,
    validate_types=True,
    backup=True
)

2. Environment-Specific Configurations

import os

env = os.getenv('ENVIRONMENT', 'development')
config = ConfigManager(f'config_{env}.yaml', auto_save=True)

# Environment-specific settings
if env == 'production':
    config.logging.level = 'WARNING'
else:
    config.logging.level = 'DEBUG'

3. Configuration Inheritance

from yamleaf.utils import merge_configs

# Load base configuration
base = ConfigManager('base_config.yaml', auto_save=False)

# Load environment overrides
env_overrides = ConfigManager('production_overrides.yaml', auto_save=False)

# Merge configurations
merged_config = merge_configs(base.raw_config, env_overrides.raw_config)

# Create final configuration
config = ConfigManager(
    'final_config.yaml',
    create_if_missing=True,
    default_config=merged_config
)

4. Configuration Validation

from yamleaf.utils import validate_config_structure

# Define required structure
required_schema = {
    'database': dict,
    'api': dict,
    'logging': dict
}

config = ConfigManager('config.yaml')
errors = validate_config_structure(config.raw_config, required_schema)

if errors:
    print("Configuration errors found:")
    for error in errors:
        print(f"  - {error}")

Integration Examples

Flask Application

from flask import Flask
from yamleaf import ConfigManager

app = Flask(__name__)

# Load configuration
config = ConfigManager('flask_config.yaml', auto_save=True)

# Configure Flask
app.config['SECRET_KEY'] = config.flask.secret_key
app.config['SQLALCHEMY_DATABASE_URI'] = config.database.url

# Runtime configuration updates
@app.route('/admin/config')
def update_config():
    config.features.maintenance_mode = True
    return "Maintenance mode enabled"

FastAPI Application

from fastapi import FastAPI
from yamleaf import ConfigManager

config = ConfigManager('fastapi_config.yaml')
app = FastAPI(
    title=config.api.title,
    version=config.api.version,
    debug=config.app.debug
)

@app.on_event("startup")
async def startup_event():
    # Initialize services based on config
    if config.services.redis.enabled:
        # Initialize Redis connection
        pass

Celery Worker

from celery import Celery
from yamleaf import ConfigManager

config = ConfigManager('celery_config.yaml')

app = Celery(
    config.celery.name,
    broker=config.celery.broker_url,
    backend=config.celery.result_backend
)

# Dynamic task routing
app.conf.task_routes = config.celery.task_routes.to_dict()

Performance Considerations

The package is designed for configuration management, not high-frequency data access. For best performance:

  1. Load once: Create the ConfigManager instance once and reuse it
  2. Cache frequently accessed values: Store commonly used config values in variables
  3. Use raw_config for bulk operations: For large-scale config processing, use config.raw_config
# Good: Load once, reuse
config = ConfigManager('config.yaml')
db_host = config.database.host  # Cache if used frequently

# Avoid: Loading repeatedly
# config = ConfigManager('config.yaml')  # Don't do this in loops

Troubleshooting

Common Issues

  1. AttributeError on missing keys:
# Use get() with defaults for optional keys
timeout = config.get('api.timeout', 30)

# Or check existence first
if hasattr(config.api, 'timeout'):
    timeout = config.api.timeout
  1. Type validation errors:
# Ensure your YAML types match dataclass expectations
# Or disable validation for flexible configs
config = ConfigManager('config.yaml', validate_types=False)
  1. File permission errors:
# Ensure write permissions for auto-save
config = ConfigManager('config.yaml', auto_save=False)
# Manually save when needed
config.save()

Requirements

  • Python 3.8+
  • ruamel.yaml >= 0.17.0

License

MIT License - see LICENSE file for details.

Contributing

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

Development Setup

git clone https://github.com/yourusername/yamleaf.git
cd yamleaf
pip install -e ".[dev]"
pytest

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=yamleaf

# Run specific test
pytest tests/test_config_manager.py::TestConfigManager::test_attribute_access

Changelog

Version 0.1.0

  • Initial release
  • Dictionary and attribute access patterns
  • Type validation with dataclass integration
  • Auto-save functionality
  • Backup support
  • List operations support
  • YAML structure preservation
  • Comprehensive test suite

Author

Created by Aaron Z. (mildthrone@proton.me)

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

yamleaf-0.1.0.tar.gz (70.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yamleaf-0.1.0-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file yamleaf-0.1.0.tar.gz.

File metadata

  • Download URL: yamleaf-0.1.0.tar.gz
  • Upload date:
  • Size: 70.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for yamleaf-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e9148a326da298c8fca9b61a3cd38ac34d11b4b0488a4f5c5dcfb4b7481b2948
MD5 57d0efbb1eb4c5b38e12278f0315c910
BLAKE2b-256 42b22cd6f20fb273bbbcd75d60dd8e90abb27e7cd31b4b22153d133e90bacad7

See more details on using hashes here.

File details

Details for the file yamleaf-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: yamleaf-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.1

File hashes

Hashes for yamleaf-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0cf144df192e2d2c0de137501817a6f47d9cb4207956bd10f7ce3b11a05d03a9
MD5 a0b74dbb4801102c1f3d0c8ac24d1293
BLAKE2b-256 5830273be2d3483384be5ae6a610d977c61a2af772353469475d997f98e3751e

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