Skip to content

Configuration API Reference

Configuration management handles device configuration storage and persistence.

BaseConfigManager

ucapi_framework.config.BaseConfigManager

BaseConfigManager(data_path: str, add_handler: Callable[[DeviceT], None] | None = None, remove_handler: Callable[[DeviceT | None], None] | None = None, config_class: type[DeviceT] | None = None)

Bases: Generic[DeviceT]

Base class for device configuration management.

Handles: - Loading/storing configuration from/to JSON - CRUD operations (add, update, remove, get) - Configuration callbacks - Optional backup/restore support

Class Type Parameters:

Name Bound or Constraints Description Default
DeviceT

The device configuration dataclass type

required

Create a configuration instance.

:param data_path: Configuration path for the configuration file :param add_handler: Optional callback when device is added :param remove_handler: Optional callback when device is removed :param config_class: The configuration dataclass type (optional, auto-detected from type hints if not provided)

Attributes

data_path property

data_path: str

Return the configuration path.

Functions

all

all() -> Iterator[DeviceT]

Get an iterator for all device configurations.

contains

contains(device_id: str) -> bool

Check if there's a device with the given device identifier.

:param device_id: Device identifier :return: True if device exists

add_or_update

add_or_update(device: DeviceT) -> None

Add a new device or update if it already exists.

:param device: Device configuration to add or update

get

get(device_id: str) -> DeviceT | None

Get device configuration for given identifier.

:param device_id: Device identifier :return: Device configuration or None

update

update(device: DeviceT) -> bool

Update a configured device and persist configuration.

:param device: Device configuration with updated values :return: True if device was updated, False if not found

remove

remove(device_id: str) -> bool

Remove the given device configuration.

:param device_id: Device identifier :return: True if device was removed

clear

clear() -> None

Remove all configuration.

store

store() -> bool

Store the configuration file.

:return: True if the configuration could be saved

load

load() -> bool

Load the configuration from file.

:return: True if the configuration could be loaded

get_device_id

get_device_id(device: DeviceT) -> str

Extract device identifier from device configuration.

Default implementation: tries common attribute names (identifier, id, device_id). Override this if your device config uses a different attribute name.

:param device: Device configuration :return: Device identifier :raises AttributeError: If no valid ID attribute is found

get_backup_json

get_backup_json() -> str

Get configuration as JSON string for backup.

:return: JSON string representation of configuration

restore_from_backup_json

restore_from_backup_json(backup_json: str) -> bool

Restore configuration from JSON string.

:param backup_json: JSON string containing configuration backup :return: True if restore was successful

migration_required

migration_required() -> bool

Check if configuration migration is required.

Override this method to implement migration detection logic.

:return: True if migration is required

migrate async

migrate() -> bool

Migrate configuration if required.

Override this method to implement migration logic.

:return: True if migration was successful

deserialize_device_auto

deserialize_device_auto(data: dict, device_class: type[DeviceT]) -> DeviceT | None

Automatically deserialize device configuration with nested dataclass support.

This helper method automatically handles: - Nested dataclasses - Lists of dataclasses (e.g., list[LutronLightInfo]) - Primitive types

Use this in your deserialize_device() implementation:

Example

def deserialize_device(self, data: dict) -> MyDeviceConfig | None: return self.deserialize_device_auto(data, MyDeviceConfig)

For backward compatibility or custom logic, override specific fields:

Example

def deserialize_device(self, data: dict) -> MyDeviceConfig | None: # Let auto-deserialize handle nested dataclasses device = self.deserialize_device_auto(data, MyDeviceConfig) if device: # Add custom migration logic if not hasattr(device, 'new_field'): device.new_field = "default_value" return device

:param data: Dictionary with device data :param device_class: The device dataclass type :return: Device configuration or None if invalid

deserialize_device

deserialize_device(data: dict) -> DeviceT | None

Deserialize device configuration from dictionary.

DEFAULT IMPLEMENTATION: Uses deserialize_device_auto() with the config class provided during initialization or inferred from the Generic type parameter.

Most integrations can use the default implementation without overriding:

class MyConfigManager(BaseConfigManager[MyDeviceConfig]):
    pass  # No override needed!

Or explicitly pass the config class:

manager = MyConfigManager(data_path, config_class=MyDeviceConfig)

Override only if you need custom logic:

def deserialize_device(self, data: dict) -> MyDeviceConfig | None:
    # Auto-deserialize handles nested dataclasses
    device = self.deserialize_device_auto(data, MyDeviceConfig)
    if device:
        # Custom migration logic
        if 'old_field' in data:
            device.new_field = migrate_value(data['old_field'])
        # Custom post-processing
        for light in device.lights:
            light.name = light.name.replace("_", " ")
    return device

:param data: Dictionary with device data :return: Device configuration or None if invalid

update_device_fields

update_device_fields(existing: DeviceT, updated: DeviceT) -> None

Update fields of existing device with values from updated device.

Default implementation updates all fields. Override for custom behavior.

:param existing: Existing device configuration (will be modified) :param updated: Updated device configuration (source of new values)