class ConfigurationResolver: 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 _recursive_resolve(self, current_config, root_config, path=""): """ Recursively resolves `link` entries in the configuration. """ if isinstance(current_config, dict): for key, value in list(current_config.items()): if key == "link": try: print(f"Resolving link '{value}' at path '{path}'") # Debugging target = self._find_entry(root_config, value.lower().replace(" ", "_")) if isinstance(target, dict): current_config.clear() current_config.update(target) elif isinstance(target, str): current_config[key] = target else: raise ValueError(f"Expected a dictionary or string for link '{value}', got {type(target)}") except KeyError as e: raise ValueError( f"Key error while resolving link '{value}': {str(e)}. Current path: {key}, Current config: {current_config}" ) else: self._recursive_resolve(value, root_config, path=f"{path}.{key}") elif isinstance(current_config, list): for index, item in enumerate(current_config): self._recursive_resolve(item, root_config, path=f"{path}[{index}]") def _find_entry(self, config, path): """ Finds an entry in the configuration by a dot-separated path. Supports both dictionaries and lists, but does not navigate into `subitems` unless explicitly required by the path. """ parts = path.split('.') current = config for part in parts: part = part.replace(" ", "_") # Normalize the part name if isinstance(current, list): # Look for a matching entry in the list found = next( ( item for item in current if isinstance(item, dict) and item.get("name", "").lower().replace(" ", "_") == part ), None ) if not found: raise KeyError( f"No matching entry for '{part}' in list. Path so far: {' > '.join(parts[:parts.index(part)+1])}. " f"Current list: {current}" ) current = found elif isinstance(current, dict): # Look for a key match in the dictionary key = next((k for k in current if k.lower().replace(" ", "_") == part), None) if key is None: raise KeyError( f"Key '{part}' not found in dictionary. Path so far: {' > '.join(parts[:parts.index(part)+1])}. " f"Current dictionary: {current}" ) current = current[key] else: raise ValueError( f"Invalid path segment '{part}'. Current type: {type(current)}. " f"Path so far: {' > '.join(parts[:parts.index(part)+1])}" ) # Stop navigating into `subitems` if the path doesn't explicitly require it if isinstance(current, dict) and "subitems" in current and isinstance(current["subitems"], list): if part == "subitems": current = current["subitems"] else: break # Do not navigate further unless explicitly in the path # Ensure the resolved target is a dictionary or string if not isinstance(current, (dict, str)): raise ValueError(f"Expected a dictionary or string for path '{path}', got {type(current)}. Current value: {current}") return current def get_config(self): """ Returns the resolved configuration. """ return self.config