Coverage for ovos_core/transformers.py: 72%
132 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-17 13:44 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-17 13:44 +0000
1from typing import Optional, List
2from ovos_config import Configuration
3from ovos_plugin_manager.intent_transformers import find_intent_transformer_plugins
4from ovos_plugin_manager.metadata_transformers import find_metadata_transformer_plugins
5from ovos_plugin_manager.text_transformers import find_utterance_transformer_plugins
7from ovos_plugin_manager.templates.pipeline import IntentHandlerMatch
8from ovos_utils.json_helper import merge_dict
9from ovos_utils.log import LOG
12class UtteranceTransformersService:
14 def __init__(self, bus, config=None):
15 self.config_core = config or Configuration()
16 self.loaded_plugins = {}
17 self.has_loaded = False
18 self.bus = bus
19 self.config = self.config_core.get("utterance_transformers") or {}
20 self.load_plugins()
22 @staticmethod
23 def find_plugins():
24 return find_utterance_transformer_plugins().items()
26 def load_plugins(self):
27 for plug_name, plug in self.find_plugins():
28 if plug_name in self.config:
29 # if disabled skip it
30 if not self.config[plug_name].get("active", True):
31 continue
32 try:
33 self.loaded_plugins[plug_name] = plug()
34 LOG.info(f"loaded utterance transformer plugin: {plug_name}")
35 except Exception as e:
36 LOG.error(e)
37 LOG.exception(f"Failed to load utterance transformer plugin: {plug_name}")
39 @property
40 def plugins(self):
41 """
42 Return loaded transformers in priority order, such that modules with a
43 higher `priority` rank are called first and changes from lower ranked
44 transformers are applied last
46 A plugin of `priority` 1 will override any existing context keys and
47 will be the last to modify utterances`
48 """
49 return sorted(self.loaded_plugins.values(),
50 key=lambda k: k.priority, reverse=True)
52 def shutdown(self):
53 for module in self.plugins:
54 try:
55 module.shutdown()
56 except:
57 pass
59 def transform(self, utterances: List[str], context: Optional[dict] = None):
60 context = context or {}
62 for module in self.plugins:
63 try:
64 utterances, data = module.transform(utterances, context)
65 _safe = {k:v for k,v in data.items() if k != "session"} # no leaking TTS/STT creds in logs
66 LOG.debug(f"{module.name}: {_safe}")
67 context = merge_dict(context, data)
68 except Exception as e:
69 LOG.warning(f"{module.name} transform exception: {e}")
70 return utterances, context
73class MetadataTransformersService:
75 def __init__(self, bus, config=None):
76 self.config_core = config or Configuration()
77 self.loaded_plugins = {}
78 self.has_loaded = False
79 self.bus = bus
80 self.config = self.config_core.get("metadata_transformers") or {}
81 self.load_plugins()
83 @staticmethod
84 def find_plugins():
85 return find_metadata_transformer_plugins().items()
87 def load_plugins(self):
88 for plug_name, plug in self.find_plugins():
89 if plug_name in self.config:
90 # if disabled skip it
91 if not self.config[plug_name].get("active", True):
92 continue
93 try:
94 self.loaded_plugins[plug_name] = plug()
95 LOG.info(f"loaded metadata transformer plugin: {plug_name}")
96 except Exception as e:
97 LOG.error(e)
98 LOG.exception(f"Failed to load metadata transformer plugin: {plug_name}")
100 @property
101 def plugins(self):
102 """
103 Return loaded transformers in priority order, such that modules with a
104 higher `priority` rank are called first and changes from lower ranked
105 transformers are applied last.
107 A plugin of `priority` 1 will override any existing context keys
108 """
109 return sorted(self.loaded_plugins.values(),
110 key=lambda k: k.priority, reverse=True)
112 def shutdown(self):
113 for module in self.plugins:
114 try:
115 module.shutdown()
116 except:
117 pass
119 def transform(self, context: Optional[dict] = None):
120 """
121 Sequentially applies all loaded metadata transformer plugins to the provided context.
123 Each plugin's `transform` method is called in order of descending priority, and the resulting data is merged into the context. Sensitive session data is excluded from debug logs. Exceptions raised by plugins are logged as warnings and do not interrupt the transformation process.
125 Args:
126 context: Optional dictionary containing metadata to be transformed.
128 Returns:
129 The updated context dictionary after all transformations.
130 """
131 context = context or {}
133 for module in self.plugins:
134 try:
135 data = module.transform(context)
136 _safe = {k:v for k,v in data.items() if k != "session"} # no leaking TTS/STT creds in logs
137 LOG.debug(f"{module.name}: {_safe}")
138 context = merge_dict(context, data)
139 except Exception as e:
140 LOG.warning(f"{module.name} transform exception: {e}")
141 return context
144class IntentTransformersService:
146 def __init__(self, bus, config=None):
147 """
148 Initializes the IntentTransformersService with the provided message bus and configuration.
150 Loads and prepares intent transformer plugins based on the configuration, making them ready for use.
151 """
152 self.config_core = config or Configuration()
153 self.loaded_plugins = {}
154 self.has_loaded = False
155 self.bus = bus
156 self.config = self.config_core.get("intent_transformers") or {}
157 self.load_plugins()
159 @staticmethod
160 def find_plugins():
161 """
162 Discovers and returns available intent transformer plugins.
164 Returns:
165 An iterable of (plugin_name, plugin_class) pairs for all discovered intent transformer plugins.
166 """
167 return find_intent_transformer_plugins().items()
169 def load_plugins(self):
170 """
171 Loads and initializes enabled intent transformer plugins based on the configuration.
173 Plugins marked as inactive in the configuration are skipped. Successfully loaded plugins are added to the internal registry, while failures are logged without interrupting the loading process.
174 """
175 for plug_name, plug in self.find_plugins():
176 if plug_name in self.config:
177 # if disabled skip it
178 if not self.config[plug_name].get("active", True):
179 continue
180 try:
181 self.loaded_plugins[plug_name] = plug()
182 self.loaded_plugins[plug_name].bind(self.bus)
183 LOG.info(f"loaded intent transformer plugin: {plug_name}")
184 except Exception as e:
185 LOG.error(e)
186 LOG.exception(f"Failed to load intent transformer plugin: {plug_name}")
188 @property
189 def plugins(self):
190 """
191 Returns the loaded intent transformer plugins sorted by priority.
192 """
193 return sorted(self.loaded_plugins.values(),
194 key=lambda k: k.priority, reverse=True)
196 def shutdown(self):
197 """
198 Shuts down all loaded plugins, suppressing any exceptions raised during shutdown.
199 """
200 for module in self.plugins:
201 try:
202 module.shutdown()
203 except:
204 pass
206 def transform(self, intent: IntentHandlerMatch) -> IntentHandlerMatch:
207 """
208 Sequentially applies all loaded intent transformer plugins to the given intent object.
210 Each plugin's `transform` method is called in order of priority. Exceptions raised by individual plugins are logged as warnings, and processing continues with the next plugin. The final, transformed intent object is returned.
212 Args:
213 intent: The intent match object to be transformed.
215 Returns:
216 The transformed intent match object after all plugins have been applied.
217 """
218 for module in self.plugins:
219 try:
220 intent = module.transform(intent)
221 LOG.debug(f"{module.name}: {intent}")
222 except Exception as e:
223 LOG.warning(f"{module.name} transform exception: {e}")
224 return intent