Files
port-ui/app/utils/configuration_resolver.py

155 lines
6.3 KiB
Python
Raw Normal View History

2025-01-09 13:53:56 +01:00
class ConfigurationResolver:
"""
A class to resolve `link` entries in a nested configuration structure.
Supports navigation through dictionaries, lists, and `children`.
2025-01-09 13:53:56 +01:00
"""
def __init__(self, config):
self.config = config
def resolve_links(self):
"""
Resolves all `link` entries in the configuration.
"""
self._recursive_resolve(self.config, self.config)
def _replace_in_list_by_list(self, list_origine, old_element, new_elements):
index = list_origine.index(old_element)
list_origine[index : index + 1] = new_elements
def _replace_element_in_list(self, list_origine, old_element, new_element):
index = list_origine.index(old_element)
list_origine[index] = new_element
2025-01-09 13:53:56 +01:00
def _recursive_resolve(self, current_config, root_config):
"""
Recursively resolves `link` entries in the configuration.
"""
if isinstance(current_config, dict):
for key, value in list(current_config.items()):
if key == "children":
2025-01-15 02:49:10 +01:00
if value is None or not isinstance(value, list):
raise ValueError(
"Expected 'children' to be a list, but got "
f"{type(value).__name__} instead."
)
for item in value:
if "link" in item:
loaded_link = self._find_entry(
root_config,
self._mapped_key(item["link"]),
False,
)
if isinstance(loaded_link, list):
self._replace_in_list_by_list(value, item, loaded_link)
else:
self._replace_element_in_list(value, item, loaded_link)
2025-01-15 02:08:49 +01:00
else:
self._recursive_resolve(value, root_config)
elif key == "link":
2025-01-09 13:56:29 +01:00
try:
loaded = self._find_entry(
root_config, self._mapped_key(value), False
)
2025-01-15 02:08:49 +01:00
if isinstance(loaded, list) and len(loaded) > 2:
loaded = self._find_entry(
root_config, self._mapped_key(value), False
)
2025-01-15 02:08:49 +01:00
current_config.clear()
current_config.update(loaded)
except Exception as e:
2025-01-09 13:56:29 +01:00
raise ValueError(
f"Error resolving link '{value}': {str(e)}. "
f"Current part: {key}, Current config: {current_config}"
+ (
f", Loaded: {loaded}"
if "loaded" in locals() or "loaded" in globals()
else ""
)
2025-01-09 13:56:29 +01:00
)
2025-01-09 13:53:56 +01:00
else:
self._recursive_resolve(value, root_config)
elif isinstance(current_config, list):
for item in current_config:
self._recursive_resolve(item, root_config)
def _get_children(self, current):
if isinstance(current, dict) and (
"children" in current and current["children"]
):
current = current["children"]
2025-01-10 13:56:37 +01:00
return current
def _mapped_key(self, name):
2025-01-17 02:14:48 +01:00
return name.replace(" ", "").lower()
def _find_by_name(self, current, part):
2025-01-10 13:56:37 +01:00
return next(
(
item
for item in current
if isinstance(item, dict)
and self._mapped_key(item.get("name", "")) == part
),
None,
)
2025-01-10 13:56:37 +01:00
def _find_entry(self, config, path, children):
2025-01-09 13:53:56 +01:00
"""
Finds an entry in the configuration by a dot-separated path.
Supports both dictionaries and lists with `children` navigation.
2025-01-09 13:53:56 +01:00
"""
parts = path.split(".")
2025-01-09 13:53:56 +01:00
current = config
for part in parts:
if isinstance(current, list):
if part != "children":
found = self._find_by_name(current, part)
if found:
current = found
print(
f"Matching entry for '{part}' in list. Path so far: "
f"{' > '.join(parts[: parts.index(part) + 1])}. "
f"Current list: {current}"
)
else:
raise ValueError(
f"No matching entry for '{part}' in list. Path so far: "
f"{' > '.join(parts[: parts.index(part) + 1])}. "
f"Current list: {current}"
)
2025-01-09 13:53:56 +01:00
elif isinstance(current, dict):
2025-01-17 02:14:48 +01:00
key = next((k for k in current if self._mapped_key(k) == part), None)
2025-01-09 13:53:56 +01:00
if key is None:
2025-04-10 14:01:53 +02:00
if "children" not in current:
raise KeyError(
"No 'children' found in current dictionary. Path so far: "
f"{' > '.join(parts[: parts.index(part) + 1])}. "
f"Current dictionary: {current}"
)
current = self._find_by_name(current["children"], part)
2025-01-10 13:56:37 +01:00
if not current:
raise KeyError(
f"Key '{part}' not found in dictionary. Path so far: "
f"{' > '.join(parts[: parts.index(part) + 1])}. "
2025-01-10 13:56:37 +01:00
f"Current dictionary: {current}"
)
else:
2025-01-10 13:56:37 +01:00
current = current[key]
2025-01-09 13:53:56 +01:00
else:
2025-01-09 13:56:29 +01:00
raise ValueError(
f"Invalid path segment '{part}'. Current type: {type(current)}. "
f"Path so far: {' > '.join(parts[: parts.index(part) + 1])}"
2025-01-09 13:56:29 +01:00
)
if children:
current = self._get_children(current)
2025-01-09 13:53:56 +01:00
return current
def get_config(self):
"""
Returns the resolved configuration.
"""
2025-01-09 13:56:29 +01:00
return self.config