config.md 9.3 KB

trixy_core.config

Konfigurationssystem mit Validierung, Environment-Substitution, Vererbung und Migration.

Übersicht

from trixy_core.config import (
    # Core
    ConfigManager,
    ServerConfig, ClientConfig, StandaloneConfig, TrainerConfig,
    # Environment
    EnvironmentSubstitutor, substitute_env,
    # Validation
    ConfigSchema, ConfigFieldSpec, validate_config,
    # Inheritance
    ConfigInheritance, deep_merge,
    # CLI
    CLIConfigMerger, CLIOption,
    # Secrets
    SecretsProvider, EncryptedSecretsProvider,
    EnvironmentSecretsProvider, FileSecretsProvider,
    # Migration
    ConfigMigration, ConfigMigrator,
)

ConfigManager

Lädt und verwaltet Konfigurationen.

from trixy_core.config import ConfigManager

# Manager erstellen
manager = ConfigManager()

# Konfiguration laden
config = manager.load("config/server_config.json")

# Mit Validierung
config = manager.load(
    "config/server_config.json",
    schema=ServerConfigSchema,
    validate=True,
)

# Als Dataclass
server_config: ServerConfig = manager.load_as(
    "config/server_config.json",
    ServerConfig,
)

# Werte abrufen
port = config.get("server.port", default=2101)
debug = config.get("debug", default=False)

# Dateiüberwachung
manager.watch("config/server_config.json", callback=on_config_changed)

# Konfiguration speichern
manager.save("config/server_config.json", config)

Auto-Reload

from trixy_core.config import ConfigManager

manager = ConfigManager(auto_reload=True)

@manager.on_change("config/server_config.json")
def handle_config_change(old_config, new_config):
    print("Konfiguration geändert")
    apply_new_config(new_config)

Konfigurationsklassen

ServerConfig

from trixy_core.config import ServerConfig

@dataclass
class ServerConfig:
    host: str = "0.0.0.0"
    port: int = 2101
    debug: bool = False

    # Netzwerk
    command_port: int = 2101
    audio_in_port: int = 2102
    audio_out_port: int = 2103
    music_port: int = 2104

    # Sicherheit
    encryption_key: str = ""
    require_registration: bool = True
    registration_timeout: int = 60

    # Audio
    sample_rate: int = 16000
    channels: int = 1
    chunk_size: int = 1024

    # Plugins
    plugin_directory: str = "plugins"
    enabled_plugins: list[str] = field(default_factory=list)

ClientConfig

from trixy_core.config import ClientConfig

@dataclass
class ClientConfig:
    server_host: str = ""
    server_port: int = 2101
    room_id: str = ""
    alias: str = ""

    # Wakeword
    wakeword_model: str = "default"
    wakeword_sensitivity: float = 0.5

    # Audio
    audio_device: str = "default"
    sample_rate: int = 16000

Environment-Substitution

Ersetzt Umgebungsvariablen in Konfigurationen.

from trixy_core.config import EnvironmentSubstitutor, substitute_env

# JSON-Konfiguration
config = {
    "database": {
        "host": "${DB_HOST}",
        "port": "${DB_PORT:-5432}",
        "password": "${DB_PASSWORD}",
    },
    "api_key": "${API_KEY:-default_key}",
}

# Substitution durchführen
substitutor = EnvironmentSubstitutor()
result = substitutor.substitute(config)

# Oder einfach
result = substitute_env(config)

# Ergebnis (mit DB_HOST=localhost, DB_PASSWORD=secret)
# {
#     "database": {
#         "host": "localhost",
#         "port": "5432",        # Default verwendet
#         "password": "secret",
#     },
#     "api_key": "default_key",  # Default verwendet
# }

Syntax

Pattern Beschreibung
${VAR} Pflicht-Variable
${VAR:-default} Mit Default-Wert
${VAR:?error} Fehler wenn nicht gesetzt

Validierung

Schema-basierte Konfigurationsvalidierung.

from trixy_core.config import (
    ConfigSchema,
    ConfigFieldSpec,
    ConfigValueType,
    validate_config,
    ConfigValidationError,
)

# Schema definieren
schema = ConfigSchema(
    fields={
        "host": ConfigFieldSpec(
            type=ConfigValueType.STRING,
            required=True,
            pattern=r"^[\w.-]+$",
        ),
        "port": ConfigFieldSpec(
            type=ConfigValueType.INTEGER,
            required=True,
            min_value=1,
            max_value=65535,
        ),
        "debug": ConfigFieldSpec(
            type=ConfigValueType.BOOLEAN,
            default=False,
        ),
        "plugins": ConfigFieldSpec(
            type=ConfigValueType.LIST,
            item_type=ConfigValueType.STRING,
        ),
    }
)

# Validieren
try:
    validated = validate_config(config, schema)
except ConfigValidationError as e:
    for error in e.errors:
        print(f"{error.field}: {error.message}")

Schema aus Dataclass

from trixy_core.config import schema_from_dataclass

@dataclass
class MyConfig:
    name: str
    count: int = 10
    enabled: bool = True

schema = schema_from_dataclass(MyConfig)
validated = validate_config(config, schema)

Konfigurationsvererbung

Mehrschichtige Konfiguration mit Overrides.

from trixy_core.config import ConfigInheritance, deep_merge

inheritance = ConfigInheritance()

# Basis-Konfiguration
inheritance.add_layer("base", {
    "logging": {"level": "INFO"},
    "server": {"host": "localhost", "port": 8080},
})

# Environment-Override
inheritance.add_layer("production", {
    "logging": {"level": "WARNING"},
    "server": {"host": "0.0.0.0"},
})

# Lokale Overrides
inheritance.add_layer("local", {
    "debug": True,
})

# Zusammenführen
config = inheritance.merge()

# Ergebnis:
# {
#     "logging": {"level": "WARNING"},
#     "server": {"host": "0.0.0.0", "port": 8080},
#     "debug": True,
# }

Dateibasiert

from trixy_core.config import ConfigInheritance

inheritance = ConfigInheritance()

# Dateien in Reihenfolge laden
inheritance.load_file("config/base.json")
inheritance.load_file("config/production.json")
inheritance.load_file("config/local.json", optional=True)

config = inheritance.merge()

CLI-Integration

Konfiguration aus Kommandozeilen-Argumenten.

from trixy_core.config import CLIConfigMerger, CLIOption

merger = CLIConfigMerger()

# Optionen definieren
merger.add_option(CLIOption(
    name="--host",
    config_path="server.host",
    type=str,
))

merger.add_option(CLIOption(
    name="--port",
    config_path="server.port",
    type=int,
))

merger.add_option(CLIOption(
    name="--debug",
    config_path="debug",
    type=bool,
    flag=True,
))

# Mit Datei-Konfiguration zusammenführen
import argparse
parser = argparse.ArgumentParser()
args = parser.parse_args()

config = merger.merge(file_config, args)

Secrets-Management

Sichere Speicherung sensibler Daten.

EncryptedSecretsProvider

from trixy_core.config.secrets import EncryptedSecretsProvider

provider = EncryptedSecretsProvider(
    key_file="secrets.key",
    secrets_file="secrets.enc",
)

# Secret setzen
provider.set("database_password", "super_secret")
provider.set("api_key", "key_12345")

# Secret abrufen
password = provider.get("database_password")

# Als SecretValue (schützt vor versehentlichem Logging)
secret = provider.get_secure("api_key")
print(secret)  # Gibt "***" aus
print(secret.reveal())  # Gibt echten Wert aus

EnvironmentSecretsProvider

from trixy_core.config.secrets import EnvironmentSecretsProvider

provider = EnvironmentSecretsProvider(prefix="TRIXY_")

# Liest TRIXY_DATABASE_PASSWORD aus Umgebung
password = provider.get("database_password")

FileSecretsProvider

from trixy_core.config.secrets import FileSecretsProvider

provider = FileSecretsProvider(
    directory=Path("/run/secrets"),
    encoding="utf-8",
)

# Liest /run/secrets/database_password
password = provider.get("database_password")

Konfigurationsmigration

Versionierte Migrationen zwischen Config-Versionen.

from trixy_core.config.versioning import (
    ConfigMigration,
    ConfigMigrator,
    MigrationResult,
)

# Migrationen definieren
class MigrateV1ToV2(ConfigMigration):
    from_version = "1.0.0"
    to_version = "2.0.0"

    def migrate(self, config: dict) -> dict:
        # Port-Feld umbenannt
        if "port" in config:
            config["server_port"] = config.pop("port")

        # Neues Pflichtfeld
        config.setdefault("encryption_enabled", True)

        return config

class MigrateV2ToV3(ConfigMigration):
    from_version = "2.0.0"
    to_version = "3.0.0"

    def migrate(self, config: dict) -> dict:
        # Strukturänderung
        config["server"] = {
            "port": config.pop("server_port"),
            "host": config.pop("host", "localhost"),
        }
        return config

# Migrator erstellen
migrator = ConfigMigrator()
migrator.register(MigrateV1ToV2())
migrator.register(MigrateV2ToV3())

# Migration durchführen
old_config = {"version": "1.0.0", "port": 8080}
result = migrator.migrate(old_config, target_version="3.0.0")

print(f"Migriert von {result.from_version} zu {result.to_version}")
print(f"Neue Konfiguration: {result.config}")

Config-Diff

from trixy_core.config.versioning import diff_configs, DiffType

old = {"a": 1, "b": {"c": 2}}
new = {"a": 1, "b": {"c": 3, "d": 4}}

diff = diff_configs(old, new)

for entry in diff.entries:
    print(f"{entry.type.name}: {entry.path} = {entry.new_value}")

# MODIFIED: b.c = 3
# ADDED: b.d = 4