Yacman Tutorial: YAMLConfigManager¶
This tutorial shows you the features of the yacman package using the modern v1.0 API with YAMLConfigManager.
First, let's prepare some test data:
import yaml
from yacman import YAMLConfigManager, write_lock, read_lock
# Sample data for demonstrations
yaml_dict = {
'cfg_version': 1.0,
'database': {
'host': 'localhost',
'port': 5432,
'name': 'mydb'
},
'features': ['logging', 'caching', 'monitoring']
}
yaml_str = """\
cfg_version: 1.0
database:
host: localhost
port: 5432
name: mydb
features:
- logging
- caching
- monitoring
"""
# Create a test file
filepath = "test_config.yaml"
with open(filepath, 'w') as f:
yaml.dump(yaml_dict, f)
Creating YAMLConfigManager objects¶
There are several ways to create a YAMLConfigManager object in v1.0:
1. From a YAML file¶
Use from_yaml_file() to load configuration from a file:
ym = YAMLConfigManager.from_yaml_file(filepath)
print(ym.to_dict())
2. From a dictionary¶
Use from_obj() to create from a Python dictionary:
ym = YAMLConfigManager.from_obj(yaml_dict)
print(ym.to_dict())
3. From a YAML string¶
Use from_yaml_data() to parse a YAML-formatted string:
ym = YAMLConfigManager.from_yaml_data(yaml_str)
print(ym.to_dict())
Accessing configuration values¶
You can access values using dictionary-style syntax:
ym = YAMLConfigManager.from_yaml_file(filepath)
# Access top-level keys
print(f"Config version: {ym['cfg_version']}")
print(f"Features: {ym['features']}")
# Access nested values
print(f"Database host: {ym['database']['host']}")
print(f"Database port: {ym['database']['port']}")
File locking and safe writes¶
YAMLConfigManager provides race-free writing with file locking, making it safe for multi-user/multi-process contexts.
Write locks¶
Use write_lock() for exclusive write access:
ym = YAMLConfigManager.from_yaml_file(filepath)
# Modify the configuration
ym['new_feature'] = 'authentication'
ym['database']['timeout'] = 30
# Write with lock
with write_lock(ym) as locked_ym:
locked_ym.rebase() # Capture any changes since file was loaded
locked_ym.write()
print("Configuration written successfully!")
Read locks¶
Use read_lock() for shared read access. Multiple processes can hold read locks simultaneously:
ym = YAMLConfigManager.from_yaml_file(filepath)
# Rebase to sync with file changes
with read_lock(ym) as locked_ym:
locked_ym.rebase()
print(f"Current config: {locked_ym.to_dict()}")
Reset vs Rebase¶
rebase(): Replays in-memory changes on top of file contentsreset(): Discards in-memory changes and loads from file
ym = YAMLConfigManager.from_yaml_file(filepath)
# Make an in-memory change
ym['temp_value'] = 'will be discarded'
print(f"Before reset: {ym.get('temp_value')}")
# Reset discards in-memory changes
with read_lock(ym) as locked_ym:
locked_ym.reset()
print(f"After reset: {ym.get('temp_value')}")
Updating from another object¶
You can merge configuration from a dictionary into an existing YAMLConfigManager:
ym = YAMLConfigManager.from_yaml_file(filepath)
# Update with additional configuration
overrides = {
'database': {'host': 'production.example.com'},
'debug': False
}
ym.update_from_obj(overrides)
print(ym.to_dict())
Environment variable expansion¶
YAMLConfigManager can expand environment variables in configuration values using the .exp property:
import os
# Set an environment variable
os.environ['DB_HOST'] = 'prod-server.example.com'
# Create config with environment variable reference
config_with_env = {
'database': {
'host': '${DB_HOST}',
'port': 5432
}
}
ym = YAMLConfigManager.from_obj(config_with_env)
# Access without expansion
print(f"Raw value: {ym['database']['host']}")
# Access with expansion
print(f"Expanded value: {ym.exp['database']['host']}")
Converting to YAML string¶
You can serialize the configuration back to a YAML string:
ym = YAMLConfigManager.from_obj(yaml_dict)
print(ym.to_yaml())
Complete example: Configuration management workflow¶
Here's a complete example showing a typical configuration management workflow:
# 1. Load base configuration
config_file = "app_config.yaml"
base_config = {
'app_name': 'MyApp',
'version': '1.0.0',
'database': {
'host': 'localhost',
'port': 5432
},
'cache': {
'enabled': True,
'ttl': 3600
}
}
# Save initial config
with open(config_file, 'w') as f:
yaml.dump(base_config, f)
# 2. Load and modify configuration
ym = YAMLConfigManager.from_yaml_file(config_file)
print("Loaded configuration:")
print(ym.to_yaml())
# 3. Apply environment-specific overrides
env_overrides = {
'database': {'host': 'prod-db.example.com'},
'cache': {'ttl': 7200}
}
ym.update_from_obj(env_overrides)
# 4. Add new configuration
ym['features'] = ['logging', 'metrics', 'tracing']
ym['deployment'] = {'region': 'us-east-1'}
# 5. Save with write lock
with write_lock(ym) as locked_ym:
locked_ym.rebase()
locked_ym.write()
print("\nFinal configuration:")
print(ym.to_yaml())
# Cleanup
import os
os.remove(config_file)
Summary¶
Key takeaways:
- Creation: Use
from_yaml_file(),from_obj(), orfrom_yaml_data()to create YAMLConfigManager objects - Access: Use dictionary-style syntax to access configuration values
- Writing: Always use
write_lock()context manager withrebase()beforewrite() - Reading: Use
read_lock()for safe concurrent reads - Updates: Use
update_from_obj()to merge configurations - Environment variables: Use
.expproperty to expand environment variables
For more details, see the API documentation.
# Cleanup test files
import os
if os.path.exists(filepath):
os.remove(filepath)
if os.path.exists('test_config.yaml'):
os.remove('test_config.yaml')