Coverage for packages / dqm-ml-core / src / dqm_ml_core / utils / registry.py: 88%
40 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 10:11 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 10:11 +0000
1"""Plugin registry for dynamically loading DQM components.
3This module contains the PluginLoadedRegistry class and load_registered_plugins
4function for discovering and loading metric processors, data loaders,
5and output writers via Python entry points.
6"""
8from importlib.metadata import EntryPoints, entry_points
9import logging
10import sys
11from typing import Any
13from dqm_ml_core import DatametricProcessor
15logger = logging.getLogger(__name__)
18# TODO once a base class for all registry created, dict shall have dict[str, base_class]
19def load_registered_plugins(plugin_group: str, base_class: Any, base_name: str = "default") -> dict[str, Any]:
20 """Discover and load plugins registered via Python entry points.
22 Args:
23 plugin_group: The entry point group name (e.g., 'dqm_ml.metrics').
24 base_class: Optional base class to verify plugin type safety.
25 base_name: Name of the base class to ignore during discovery.
27 Returns:
28 A dictionary mapping plugin names to their loaded classes.
29 """
30 try:
31 # python 3.10+
32 plugin_entry_points: EntryPoints = entry_points(group=plugin_group)
33 except TypeError:
34 # Old version for older python version
35 logger.warning(f"Old python version not supported: {sys.version_info}")
36 return {}
38 registry = {}
39 for v in plugin_entry_points:
40 # Filter base class registry (not callable)
41 if v.name != base_name: 41 ↛ 39line 41 didn't jump to line 39 because the condition on line 41 was always true
42 obj = v.load()
43 if base_class is None or issubclass(obj, base_class): 43 ↛ 47line 43 didn't jump to line 47 because the condition on line 43 was always true
44 logger.debug(f"Referencing {plugin_group} - {v.name} class {obj} from {base_class}")
45 registry[v.name] = obj
46 else:
47 logger.error(f"Entry point {plugin_group} - {v.name} class {obj} not derived from {base_class} ignored")
49 # return a dict to class builder registry
50 return registry
53class PluginLoadedRegistry:
54 """
55 Singleton registry that provides lazy access to all registered DQM components.
57 Components include:
58 - Metrics (DatametricProcessor)
59 - DataLoaders
60 - OutputWriters
61 """
63 _metrics_registry: dict[str, type[DatametricProcessor]] | None = None
64 _dataloaders_registry: dict[str, Any] | None = None
65 _outputwriter_registry: dict[str, Any] | None = None
67 @classmethod
68 def get_metrics_registry(cls) -> dict[str, type[DatametricProcessor]]:
69 """Return the registry of available metric processors.
71 Returns:
72 A dictionary mapping metric processor names to their classes.
73 """
74 if not cls._metrics_registry:
75 cls._metrics_registry = load_registered_plugins("dqm_ml.metrics", DatametricProcessor)
77 return cls._metrics_registry
79 @classmethod
80 def get_dataloaders_registry(cls) -> dict[str, Any]:
81 """Return the registry of available data loaders.
83 Returns:
84 A dictionary mapping data loader names to their classes.
85 """
86 if not cls._dataloaders_registry:
87 cls._dataloaders_registry = load_registered_plugins("dqm_ml.dataloaders", None) # TODO add base class
88 return cls._dataloaders_registry
90 @classmethod
91 def get_outputwriter_registry(cls) -> dict[str, Any]:
92 """Return the registry of available output writers.
94 Returns:
95 A dictionary mapping output writer names to their classes.
96 """
97 if not cls._outputwriter_registry:
98 cls._outputwriter_registry = load_registered_plugins("dqm_ml.outputwriter", None) # TODO add base class
100 return cls._outputwriter_registry