Skip to content

Latest commit

 

History

History
276 lines (199 loc) · 8.6 KB

File metadata and controls

276 lines (199 loc) · 8.6 KB

Dynamic Config Manager – User Guide

This guide explains how to install and use Dynamic Config Manager to manage application settings.

Installation

pip install dynamic-config-manager
# Install optional YAML, TOML and watching extras
pip install dynamic-config-manager[all]

Defining Configuration Models

Create Pydantic models for your settings. Use DynamicBaseSettings together with ConfigField to attach metadata and validation hints.

from dynamic_config_manager import DynamicBaseSettings, ConfigField

class UIConfig(DynamicBaseSettings):
    theme: str = ConfigField("light", options=["light", "dark"])
    font_size: int = ConfigField(12, ge=8, le=32)

ConfigField wraps pydantic.Field and accepts extras such as options, ui_hint, autofix_settings and format_spec.

Nested Configuration Models

Configuration models can include other Pydantic settings classes to build hierarchies of values. Define nested models and reference them using ConfigField with a default_factory.

from typing import Optional
from dynamic_config_manager import DynamicBaseSettings, ConfigField, ConfigManager

class AppSettings(DynamicBaseSettings):
    class Nested(DynamicBaseSettings):
        nested_value: float = ConfigField(0.5, ge=0, le=1)
        deeply_nested: Optional[str] = ConfigField("deep")

    nested: Nested = ConfigField(default_factory=Nested)

app_cfg = ConfigManager.register("app", AppSettings)
app_cfg.active.nested.nested_value = 0.75
app_cfg.set_value("nested.deeply_nested", "new")

Registering a Configuration

Use ConfigManager.register to register your model and optionally enable persistence.

from dynamic_config_manager import ConfigManager

cfg = ConfigManager.register(
    "ui", UIConfig, auto_save=True, persistent=True
)
  • name – unique identifier.
  • auto_save – automatically save after set_value or attribute writes.
  • persistent – if False, keep the configuration in memory only.
  • save_path – custom file location; defaults to <default_dir>/<name>.json.

Adjust the global ConfigManager.default_dir once early in your application to control where files are written.

Accessing Values

Values can be accessed by path or attribute. Attribute access works for nested models too.

cfg.active.theme = "dark"       # validated and saved
print(cfg.active.font_size)     # 12

cfg.set_value("font_size", 16)
print(cfg.get_value("font_size"))
print(cfg.get_default("font_size"))
print(cfg.get_saved("font_size"))

# missing attributes return None unless a default is provided
print(cfg.get_value("nope"))            # None
print(cfg.get_value("nope", 42))       # 42

cfg.meta provides comprehensive metadata describing each field.

info = cfg.meta.theme
print(info["options"])          # ["light", "dark"]
print(info["description"])      # field description
print(info["ui_hint"])          # UI hint for rendering
print(info["active_value"])     # current value
print(info["default_value"])    # model default value

# Get all field names for dynamic UI generation
field_names = cfg.get_field_names()
print(field_names)              # ["theme", "font_size"]

# Get field names for nested sections
nested_fields = cfg.get_field_names("nested")  # if nested config exists
print(nested_fields)            # ["nested_value", "deeply_nested"]

Comprehensive Metadata Access

The get_metadata() method returns complete field information including:

  • description – field description from ConfigField
  • json_schema_extra – complete metadata dictionary
  • Flattened attributes – common ConfigField attributes available at top level:
    • ui_hint – UI rendering hint (e.g., "ComboBox", "Slider")
    • ui_extra – additional UI parameters (e.g., step, min/max values)
    • options – allowed values for choice fields
    • autofix_settings – auto-fixing policies for validation
    • format_spec – advanced format specifications
  • Standard metadata – type, constraints, active/default/saved values
from dynamic_config_manager import DynamicBaseSettings, ConfigField, ConfigManager

class AdvancedConfig(DynamicBaseSettings):
    port: int = ConfigField(
        default=8080,
        description="Server port number", 
        ui_hint="SpinBox",
        ui_extra={"step": 1, "suffix": " (port)"},
        ge=1024, le=65535
    )

cfg = ConfigManager.register("advanced", AdvancedConfig)
meta = cfg.get_metadata("port")

print(meta["description"])      # "Server port number"
print(meta["ui_hint"])          # "SpinBox" 
print(meta["ui_extra"])         # {"step": 1, "suffix": " (port)"}
print(meta["ge"])               # 1024 (constraint)
print(meta["active_value"])     # 8080 (current value)

Read-only accessors expose the model defaults and the last saved state:

print(cfg.default.font_size)    # 12
print(cfg.saved.theme)          # value loaded from disk

The .saved accessor is also available via .file and returns PydanticUndefined if the configuration has never been persisted.

Field Introspection

Use get_field_names() to discover all available fields in a configuration, which is useful for building dynamic UIs or programmatically working with configurations.

# Get all field names from the root level
all_fields = cfg.get_field_names()
print(all_fields)               # ["theme", "font_size", "nested.value"]

# Get field names scoped to a specific nested section
db_fields = cfg.get_field_names("database")
print(db_fields)                # ["host", "port", "username"]

# Use field names with other methods
for field in all_fields:
    value = cfg.get_value(field)
    metadata = cfg.get_metadata(field)
    print(f"{field}: {value} (type: {metadata['type'].__name__})")

The returned field names are fully compatible with get_value(), set_value(), and get_metadata() methods, making it easy to build dynamic configuration interfaces.

Persistence Helpers

Call persist() to write the current values to disk or save_as() to export using a chosen format.

cfg.persist()                   # saves as JSON by default
cfg.save_as("ui.yaml", file_format="yaml")

Use restore_value() or restore_defaults() to revert values.

cfg.restore_value("theme", source="file")
cfg.restore_defaults()

ConfigManager.save_all() and ConfigManager.restore_all_defaults() operate on every registered configuration.

Watching for File Changes

watch_and_reload starts a daemon thread that reloads configurations when their backing files are modified.

from dynamic_config_manager import watch_and_reload

thread, stop = watch_and_reload(["ui"], debounce=200)
# ... edit ui.json from another process ...
stop.set()
thread.join()

Command Line Interface

After installation the dcm-cli tool can inspect or update configuration files.

# Display a configuration file
$ dcm-cli show ui.json

# Change a value
$ dcm-cli set ui.json theme dark

Auto‑Fix System

Applying attach_auto_fix to a model enables automatic formatting and coercion of inputs before validation.

from dynamic_config_manager import attach_auto_fix

@attach_auto_fix(eval_expressions=True)
class MachineCfg(DynamicBaseSettings):
    speed: int = ConfigField(1000, ge=500, le=2000)

Setting speed to the string "10*2" will evaluate the expression and clamp it according to the policies. Policies for numerics, options, ranges and more can be controlled globally when decorating the class or per field through autofix_settings.

Runtime Model Updates

ConfigManager.update_model_field allows updating field definitions at runtime.

from dynamic_config_manager import ConfigField

ConfigManager.update_model_field(
    "ui", "font_size", ConfigField(14, ge=10, le=40)
)

The configuration instance is revalidated with the new model so updates are applied immediately.

Example Workflow

from dynamic_config_manager import (
    ConfigManager, DynamicBaseSettings, ConfigField, watch_and_reload
)

class AppCfg(DynamicBaseSettings):
    theme: str = ConfigField("light", options=["light", "dark"])
    refresh: int = ConfigField(60, ge=15, le=600)

ConfigManager.default_dir = "~/myapp/cfg"
app_cfg = ConfigManager.register("app", AppCfg, auto_save=True)

# Start watching for edits on disk
thread, stop = watch_and_reload(["app"])

# Use configuration safely throughout the app
print(app_cfg.active.theme)
app_cfg.active.refresh = 120

# Shut down watcher before exit
stop.set()
thread.join()

This covers the typical lifecycle: model definition, registration, live updates, persistence and watching for external changes.