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 fileconfig_class: Optional dataclass for type validationauto_save: Automatically save changes on exitbackup: Create backup before savingindent: YAML indentation for mapssequence_indent: YAML indentation for sequencesvalidate_types: Enable type validationcreate_if_missing: Create config file if it doesn't existdefault_config: Default configuration for new files
Methods
get(key_path: str, default: Any = None) -> Any: Get value using dot notationset(key_path: str, value: Any) -> None: Set value using dot notationupdate(updates: Dict[str, Any]) -> None: Update multiple valuesdelete(key_path: str) -> None: Delete a configuration keysave(path: Optional[Union[str, Path]] = None) -> None: Save configurationreload() -> None: Reload from file, discarding changesreset() -> None: Reset to default configuration
Properties
config: ConfigProxy: Attribute access proxyis_modified: bool: Check if configuration has been modifiedraw_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:
- Load once: Create the ConfigManager instance once and reuse it
- Cache frequently accessed values: Store commonly used config values in variables
- 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
- 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
- Type validation errors:
# Ensure your YAML types match dataclass expectations
# Or disable validation for flexible configs
config = ConfigManager('config.yaml', validate_types=False)
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9148a326da298c8fca9b61a3cd38ac34d11b4b0488a4f5c5dcfb4b7481b2948
|
|
| MD5 |
57d0efbb1eb4c5b38e12278f0315c910
|
|
| BLAKE2b-256 |
42b22cd6f20fb273bbbcd75d60dd8e90abb27e7cd31b4b22153d133e90bacad7
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cf144df192e2d2c0de137501817a6f47d9cb4207956bd10f7ce3b11a05d03a9
|
|
| MD5 |
a0b74dbb4801102c1f3d0c8ac24d1293
|
|
| BLAKE2b-256 |
5830273be2d3483384be5ae6a610d977c61a2af772353469475d997f98e3751e
|