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:


```python
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:


```python
ym = YAMLConfigManager.from_yaml_file(filepath)
print(ym.to_dict())
```

### 2. From a dictionary

Use `from_obj()` to create from a Python dictionary:


```python
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:


```python
ym = YAMLConfigManager.from_yaml_data(yaml_str)
print(ym.to_dict())
```

## Accessing configuration values

You can access values using dictionary-style syntax:


```python
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:


```python
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:


```python
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 contents
- `reset()`: Discards in-memory changes and loads from file


```python
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:


```python
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:


```python
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:


```python
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:


```python
# 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:

1. **Creation**: Use `from_yaml_file()`, `from_obj()`, or `from_yaml_data()` to create YAMLConfigManager objects
2. **Access**: Use dictionary-style syntax to access configuration values
3. **Writing**: Always use `write_lock()` context manager with `rebase()` before `write()`
4. **Reading**: Use `read_lock()` for safe concurrent reads
5. **Updates**: Use `update_from_obj()` to merge configurations
6. **Environment variables**: Use `.exp` property to expand environment variables

For more details, see the [API documentation](/yacman/code/python-api.md).


```python
# 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')
```
