This guide explains how to install and use Dynamic Config Manager to manage application settings.
pip install dynamic-config-manager
# Install optional YAML, TOML and watching extras
pip install dynamic-config-manager[all]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.
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")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_valueor 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.
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)) # 42cfg.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"]The get_metadata() method returns complete field information including:
description– field description from ConfigFieldjson_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 fieldsautofix_settings– auto-fixing policies for validationformat_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 diskThe .saved accessor is also available via .file and returns
PydanticUndefined if the configuration has never been persisted.
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.
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.
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()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 darkApplying 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.
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.
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.