Coverage for source/utils/dynamic_from_string_converter.py: 98%
42 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-07-30 20:59 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-07-30 20:59 +0000
1# utils/dynamic_from_string_converter.py
3# global imports
4import importlib
5import logging
6import pkgutil
7from anytree import Node
8from typing import Optional
9from types import ModuleType
11# local imports
12from source.utils import SingletonMeta
14class PackageNode(Node):
15 """
16 Implements a node in the package tree structure. Each node represents a package
17 and contains its name, the package itself, and a reference to its parent node.
18 """
20 def __init__(self, name: str, package: ModuleType, parent: Optional['PackageNode'] = None) -> None:
21 """
22 Class constructor. Initializes the PackageNode with a name, package, and optional parent node.
24 Parameters:
25 name (str): The name of the package.
26 package (ModuleType): The package module.
27 parent (Optional[PackageNode]): The parent node in the tree structure.
28 """
30 super().__init__(name, parent)
31 self.package: ModuleType = package
33class DynamicFromStringConverter(metaclass = SingletonMeta):
34 """
35 Implements a dynamic class converter that allows for the conversion of class names
36 from strings to actual class handles. It builds a tree structure of packages and allows
37 for the retrieval of class handles based on their names.
38 """
40 def __init__(self) -> None:
41 """
42 Class constructor. Initializes an empty list to hold registered packages.
43 """
45 self.__registered_packages: list[PackageNode] = []
47 def __build_package_tree(self, package_name: str, parent: Optional[PackageNode] = None) -> PackageNode:
48 """
49 Builds a tree structure of packages starting from the given package name.
51 Parameters:
52 package_name (str): The name of the package to build the tree for.
53 parent (Optional[PackageNode]): The parent node in the tree structure.
55 Returns:
56 (PackageNode): The root node of the package tree.
57 """
59 root_node: Optional[PackageNode] = None
60 try:
61 package = importlib.import_module(package_name)
62 root_node = PackageNode(name = package_name, package = package, parent = parent)
64 if hasattr(package, '__path__'):
65 for _, module_name, is_pkg in pkgutil.iter_modules(package.__path__, package_name + '.'):
66 if is_pkg:
67 self.__build_package_tree(module_name, parent = root_node)
68 except ImportError as e:
69 logging.error(f"Failed to import {package_name}: {e}")
71 return root_node
73 def __find_class_in_package_tree(self, class_name: str, package_node: PackageNode) -> Optional[type]:
74 """
75 Searches for a class in the package tree.
77 Parameters:
78 class_name (str): The name of the class to search for.
79 package_node (PackageNode): The root node of the package tree to search.
81 Returns:
82 (Optional[type]): The class type if found, None otherwise.
83 """
85 if hasattr(package_node.package, class_name):
86 return getattr(package_node.package, class_name)
88 for child in package_node.children:
89 found_class = self.__find_class_in_package_tree(class_name, child)
90 if found_class is not None:
91 return found_class
93 return None
96 def register_packages(self, packages_to_get_registered: list[str]) -> None:
97 """
98 Registers a list of packages by building their package trees.
100 Parameters:
101 packages_to_get_registered (list[str]): A list of package names to register.
102 """
104 self.__registered_packages: list[PackageNode] = [self.__build_package_tree(package_name) \
105 for package_name in packages_to_get_registered]
107 def get_class_handle(self, class_name: str) -> type:
108 """
109 Retrieves the class handle for a given class name from the registered packages.
111 Parameters:
112 class_name (str): The name of the class to retrieve.
114 Raises:
115 ValueError: If the class is not found in the registered packages.
117 Returns:
118 (type): The class type if found.
119 """
121 for package_tree in self.__registered_packages:
122 found_class = self.__find_class_in_package_tree(class_name, package_tree)
123 if found_class is not None:
124 return found_class
126 raise ValueError(f"Class '{class_name}' not found in registered packages.")