Configuration: config.yaml in Detail

[!WARNING] This part of the documentation is only lightly maintained. Content may be out of date or may have been partially generated by AI and could therefore contain errors.

Two-File System

The Streaming Tool strictly separates template and user configuration:

  • config.default.yaml (Template) → overwritten during updates
  • config.yaml (User) → persistent, never overwritten

Why? Updates can introduce new config keys without deleting user data.

The System

Update starts
    ↓
Checks config_version
    ↓
       default > user?
    ↙              ↘
  Yes              No
  ↓                 ↓
Migration       No change
(Merge Keys)    (User values remain)

Migration: Step by Step

1. Check version

# config.default.yaml
config_version: 2

# config.yaml (user)
config_version: 1  ← older!

2. System detects: Migration needed

3. Perform merge:

  • Adopt new keys from default → into user
  • Preserve user values (do not overwrite!)
  • Delete old keys from user →
  • Preserve comments (if before keys)

4. config.yaml rewritten with version: 2

Code: Loading Config with Fallbacks

import yaml
import sys

CONFIG_FILE = "config/config.yaml"
CONFIG_DEFAULT = "config/config.default.yaml"

def load_config():
    """Load config with error handling."""
    try:
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            cfg = yaml.safe_load(f)
        if cfg is None:
            cfg = {}
        return cfg
    except FileNotFoundError:
        print("config.yaml not found! Using defaults.")
        return load_default_config()
    except Exception as e:
        print(f"Config error: {e}")
        return {}

def load_default_config():
    """Fallback to config.default.yaml."""
    try:
        with open(CONFIG_DEFAULT) as f:
            return yaml.safe_load(f) or {}
    except:
        return {}

# Read values with defaults
cfg = load_config()
port = cfg.get("WebServer", {}).get("Port", 5000)
enabled = cfg.get("MyPlugin", {}).get("Enable", True)

Config Value Access (Best Practices)

# CORRECT: With .get() + defaults
log_level = cfg.get("Log", {}).get("Level", "INFO")

# INCORRECT: Direct access
log_level = cfg["Log"]["Level"]  # KeyError risk!

# Deep get
db_host = cfg.get("Database", {}).get("Host", "localhost")
db_port = cfg.get("Database", {}).get("Port", 5432)

# Entire section with default
timer_cfg = cfg.get("Timer", {})

Checklist for Config Changes

  • ☑ New keys → add to config.default.yaml
  • config_version incremented?
  • ☑ Comments BEFORE the first key are preserved
  • ☑ Code uses .get() with defaults
  • ☑ Test: Does migration work?

Back to Appendix

Whether a config migration is necessary is controlled via config_version. It is located at the beginning of the files:

config_version: 1
  • Data type: Integers only.
  • Logic: A migration is only triggered if the config_version in config.default.yaml is greater than the one in the current config.yaml.

Migration Process

The migration process runs recursively through all levels of the configuration file. The following rules apply:

  • Preserving user values: Values that the user has customized in their config.yaml will not be overwritten.
  • Cleanup: Keys that no longer exist in the new config.default.yaml are removed from config.yaml.
  • Completeness: New keys from the template are adopted into the user config.

Everything before the first key in config.default.yaml is not copied into config.yaml. In this case, that means all comments above config_version are not copied. Keep this in mind when making modifications.

# -------------------------------------------------------------------------
# STREAMING TOOL CONFIGURATION TEMPLATE
# -------------------------------------------------------------------------
# This file is a template.
# Personal settings should be changed in 'config.yaml' only.
# -------------------------------------------------------------------------
config_version: 1

Disabling Migration

In config.yaml, the automatic configuration update can be disabled:

auto_update_config: true

If this value is set to false, no comparison with the default file takes place.

[!WARNING] Disabling this option requires fully manual maintenance of the configuration. There is no CLI command to trigger the migration afterwards. An outdated structure can lead to errors or program crashes.

Reading Values from the Config

To use configuration values in code, config.yaml is loaded into a dictionary (here cfg). Values are then accessed via the corresponding keys.

Loading the Configuration

The following block shows the standard way of reading the file. It ensures that the program terminates in a controlled manner if a read error occurs:

import yaml
import sys

try:
    with open(CONFIG_FILE, "r", encoding="utf-8") as f:
        cfg = yaml.safe_load(f)
except Exception as e:
    print(f"Error loading config: {e}")
    input("Press Enter to exit...")
    sys.exit(1)

Usage in Code

Once the variable cfg is populated, values can be accessed. Since the migration also supports nested structures, deeper levels are accessed via multiple keys:

# Access a top-level key
auto_update = cfg.get("auto_update_config", True)

# Accessing nested values (example)
# Suppose the config has a structure like:
# database:
#   host: "localhost"
db_host = cfg.get("database", {}).get("host", "127.0.0.1")

# Using config_version for logic checks
if cfg.get("config_version", 0) < 2:
    # Specific logic for older config versions
    pass

[!TIP] Working with Dictionaries

Since the configuration is a standard Python dictionary after loading, you should familiarize yourself with advanced methods for data manipulation. This saves lines of code and prevents runtime errors.

Particularly relevant are:

  • Safe access (.get()): Avoid KeyError crashes by defining default values directly when reading.
  • Nested structures: Learn how to access deeper levels efficiently (e.g. via cfg['database']['host'] or safer chaining).
  • Type hinting: Look into how to use type hints so your IDE can assist you while coding and you know exactly whether a value should be an int, bool, or str.
  • Exceptions: Understand how to catch specific errors when parsing YAML files to show the end user helpful error messages instead of cryptic tracebacks.