Coverage for packages/dqm-ml-core/src/dqm_ml_core/utils/registry.py: 99%
56 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-21 08:27 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-07-21 08:27 +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 __future__ import annotations
10from importlib.metadata import EntryPoints, entry_points
11import logging
12import sys
13from typing import Any
15from dqm_ml_core.api.processor import Processor
17logger = logging.getLogger(__name__)
20# TODO once a base class for all registry created, dict shall have dict[str, base_class]
21def load_registered_plugins(plugin_group: str, base_class: Any, base_name: str = "default") -> dict[str, Any]:
22 """Discover and load plugins registered via Python entry points.
24 Args:
25 plugin_group: The entry point group name (e.g., 'dqm_ml.metrics').
26 base_class: Optional base class to verify plugin type safety.
27 base_name: Name of the base class to ignore during discovery.
29 Returns:
30 A dictionary mapping plugin names to their loaded classes.
31 """
32 try:
33 # python 3.10+
34 plugin_entry_points: EntryPoints = entry_points(group=plugin_group)
35 except TypeError:
36 # Old version for older python version
37 logger.warning(f"Old python version not supported: {sys.version_info}")
38 return {}
40 registry = {}
41 for v in plugin_entry_points:
42 # Filter base class registry (not callable)
43 if v.name != base_name: 43 ↛ 41line 43 didn't jump to line 41 because the condition on line 43 was always true
44 obj = v.load()
45 if base_class is None or issubclass(obj, base_class):
46 logger.debug(f"Referencing {plugin_group} - {v.name} class {obj} from {base_class}")
47 registry[v.name] = obj
48 else:
49 logger.error(f"Entry point {plugin_group} - {v.name} class {obj} not derived from {base_class} ignored")
51 # return a dict to class builder registry
52 return registry
55class PluginLoadedRegistry:
56 """
57 Singleton registry that provides lazy access to all registered DQM components.
59 Components include:
60 - Metrics (Processor)
61 - DataLoaders
62 - OutputWriters
63 """
65 _metrics_registry: dict[str, type[Processor]] | None = None
66 _features_registry: dict[str, type[Processor]] | None = None
67 _gap_registry: dict[str, type[Processor]] | None = None
68 _dataloaders_registry: dict[str, Any] | None = None
69 _outputwriter_registry: dict[str, Any] | None = None
71 @classmethod
72 def get_metrics_registry(cls) -> dict[str, type[Processor]]:
73 """Return the registry of available metric processors.
75 Returns:
76 A dictionary mapping metric processor names to their classes.
77 """
78 if not cls._metrics_registry:
79 from dqm_ml_core.api.metrics_processor import MetricsProcessor
81 cls._metrics_registry = load_registered_plugins("dqm_ml.metrics", MetricsProcessor)
83 return cls._metrics_registry
85 @classmethod
86 def get_features_registry(cls) -> dict[str, type[Processor]]:
87 """Return the registry of available feature extraction processors.
89 Returns:
90 A dictionary mapping feature processor names to their classes.
91 """
92 if not cls._features_registry:
93 from dqm_ml_core.api.features_processor import FeaturesProcessor
95 cls._features_registry = load_registered_plugins("dqm_ml.features", FeaturesProcessor)
97 return cls._features_registry
99 @classmethod
100 def get_gap_registry(cls) -> dict[str, type[Processor]]:
101 """Return the registry of available gap processors.
103 Returns:
104 A dictionary mapping gap processor names to their classes.
105 """
106 if not cls._gap_registry:
107 from dqm_ml_core.api.gap_processor import GapProcessor
109 cls._gap_registry = load_registered_plugins("dqm_ml.gap", GapProcessor)
111 return cls._gap_registry
113 @classmethod
114 def get_dataloaders_registry(cls) -> dict[str, Any]:
115 """Return the registry of available data loaders.
117 Returns:
118 A dictionary mapping data loader names to their classes.
119 """
120 if not cls._dataloaders_registry:
121 cls._dataloaders_registry = load_registered_plugins("dqm_ml.dataloaders", None) # TODO add base class
122 return cls._dataloaders_registry
124 @classmethod
125 def get_outputwriter_registry(cls) -> dict[str, Any]:
126 """Return the registry of available output writers.
128 Returns:
129 A dictionary mapping output writer names to their classes.
130 """
131 if not cls._outputwriter_registry:
132 cls._outputwriter_registry = load_registered_plugins("dqm_ml.outputwriter", None) # TODO add base class
134 return cls._outputwriter_registry