"""
This module provides the `ConfigAttributes` class, which manages configuration attributes and their associated values.
The `ConfigAttributes` class acts as a proxy for the `Config` class, allowing it to handle unique attribute management efficiently. By separating attribute storage and retrieval into its own class, the `Config` class remains clean and focused on its primary responsibilities.
"""
from typing import Any
from fastcfg.config.base import AbstractConfigUnit
from fastcfg.config.items import AbstractConfigItem, BuiltInConfigItem
from fastcfg.config.utils import create_config_dict
# Types that are classified as built-in rather than custom objects
BUILT_IN_TYPES = (int, float, str, bool, list, dict, tuple, set, object)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from fastcfg.config.interface import Config
else:
Config = None
[docs]
class ConfigAttributes(AbstractConfigUnit):
"""
Manages the actual configuration attributes and their associated values, ensuring they are stored and retrieved correctly.
This class acts as a proxy for the `Config` class, allowing it to handle unique attribute management efficiently.
By separating attribute storage and retrieval into its own class, the `Config` class remains clean and focused on its primary responsibilities.
Attributes:
__attributes (dict[str, IConfigItem]): A dictionary to store configuration attributes.
Methods:
__init__(): Initializes the `ConfigAttributes` object.
get_attribute(name): Retrieves an attribute by name.
get_attributes(): Returns all attributes.
_convert_value_to_item(value): Converts a value to an `IConfigItem`.
_add_attribute(name, value): Adds a new attribute to the configuration.
add_or_update_attribute(name, value): Adds or updates an attribute in the configuration.
"""
[docs]
def __init__(self, config: "Config"):
self.__attributes: dict[str, AbstractConfigItem] = {}
self._config: "Config" = config
[docs]
def get_attribute(self, name) -> AbstractConfigItem:
"""
Retrieves an attribute by name.
Args:
name (str): The name of the attribute to retrieve.
Returns:
IConfigItem: The configuration item associated with the given name.
Raises:
AttributeError: If the attribute does not exist.
"""
try:
return self.__attributes[name]
except KeyError as exc:
raise AttributeError(
f"Attribute `{name}` does not exist."
) from exc
[docs]
def has_attribute(self, name: str) -> bool:
"""
Returns whether an attribute exists.
"""
return name in self.__attributes
[docs]
def get_attributes(self) -> dict[str, AbstractConfigItem]:
"""
Returns all attributes.
Returns:
dict[str, IConfigItem]: A dictionary of all configuration attributes.
"""
return self.__attributes
[docs]
def _convert_value_to_item(self, value: Any) -> AbstractConfigItem:
"""
Converts a raw value to an `IConfigItem`.
Args:
value (Any): The value to convert.
Returns:
IConfigItem: The converted configuration item.
Raises:
ValueError: If the value is of an invalid data type.
"""
from fastcfg.config import Config
if isinstance(value, AbstractConfigItem):
return value
elif isinstance(value, Config):
return value
elif isinstance(value, BUILT_IN_TYPES):
return BuiltInConfigItem(value)
else:
raise ValueError("Invalid data type!")
[docs]
def _add_attribute(self, name: str, value: Any) -> AbstractConfigItem:
"""
Adds a new attribute to the configuration.
Overrides existing IConfigItem if it already exists.
Args:
name (str): The name of the attribute.
value (Any): The value of the attribute, which will be converted to an IConfigItem.
Returns:
IConfigItem: The newly added configuration item.
"""
config_item = self._convert_value_to_item(value)
# Set the parent of the config item to this Config
config_item.set_parent(self._config)
self.__attributes[name] = config_item
return config_item
[docs]
def add_or_update_attribute(self, name: str, value: Any) -> None:
"""
Adds a new attribute or updates an existing attribute in the configuration.
If the attribute already exists and is a BuiltInConfigItem, its value is updated.
If the attribute is a LiveConfigItem, it is replaced with a new value.
If the attribute does not exist, it is added as a new attribute.
Args:
name (str): The name of the attribute.
value (Any): The value of the attribute, which will be converted to an IConfigItem.
"""
if isinstance(value, dict):
# Convert dict to nested Config object
value = create_config_dict(value)
if (
name in self.__attributes
): # Existing attribute that we're overriding
config_item = self.__attributes[name]
# Only support value overriding for BuiltInConfigItems
# For LiveConfigItems, we just want to replace it with the new value
if isinstance(config_item, BuiltInConfigItem):
config_item.value = value
else:
config_item = self._add_attribute(name, value)
else: # New attribute entirely
config_item = self._add_attribute(name, value)
# Trigger validation at the end
config_item.validate()
[docs]
def remove_attribute(self, name: str) -> None:
"""
Removes an attribute from the configuration.
Args:
name (str): The name of the attribute to remove.
Raises:
AttributeError: If the attribute does not exist.
"""
if name not in self.__attributes:
raise AttributeError(f"Attribute `{name}` does not exist.")
del self.__attributes[name]