truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -22
  162. truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,63 @@
1
+ """Plugin Hook System.
2
+
3
+ This module provides a hook system for plugins to integrate
4
+ with the validation pipeline and other system events.
5
+
6
+ Hook Types:
7
+ - before_validation: Called before validation starts
8
+ - after_validation: Called after validation completes
9
+ - on_issue_found: Called when a validation issue is found
10
+ - before_profile: Called before profiling starts
11
+ - after_profile: Called after profiling completes
12
+ - on_report_generate: Called when a report is generated
13
+ - on_error: Called when an error occurs
14
+ - on_plugin_load: Called when a plugin is loaded
15
+ - on_plugin_unload: Called when a plugin is unloaded
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from .protocols import (
21
+ HookType,
22
+ HookPriority,
23
+ HookContext,
24
+ HookResult,
25
+ HookHandler,
26
+ HookRegistry,
27
+ )
28
+ from .manager import (
29
+ HookManager,
30
+ hook_manager,
31
+ )
32
+ from .decorators import (
33
+ hook,
34
+ before_validation,
35
+ after_validation,
36
+ on_issue_found,
37
+ before_profile,
38
+ after_profile,
39
+ on_report_generate,
40
+ on_error,
41
+ )
42
+
43
+ __all__ = [
44
+ # Protocols
45
+ "HookType",
46
+ "HookPriority",
47
+ "HookContext",
48
+ "HookResult",
49
+ "HookHandler",
50
+ "HookRegistry",
51
+ # Manager
52
+ "HookManager",
53
+ "hook_manager",
54
+ # Decorators
55
+ "hook",
56
+ "before_validation",
57
+ "after_validation",
58
+ "on_issue_found",
59
+ "before_profile",
60
+ "after_profile",
61
+ "on_report_generate",
62
+ "on_error",
63
+ ]
@@ -0,0 +1,367 @@
1
+ """Hook Decorators.
2
+
3
+ This module provides decorators for easily registering
4
+ hook handlers from plugin code.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from functools import wraps
10
+ from typing import Any, Callable, TypeVar
11
+
12
+ from .protocols import HookContext, HookPriority, HookResult, HookType
13
+ from .manager import hook_manager
14
+
15
+ F = TypeVar("F", bound=Callable[..., Any])
16
+
17
+
18
+ def hook(
19
+ hook_type: HookType | str,
20
+ priority: HookPriority | int = HookPriority.NORMAL,
21
+ conditions: dict[str, Any] | None = None,
22
+ plugin_id: str | None = None,
23
+ auto_register: bool = True,
24
+ ) -> Callable[[F], F]:
25
+ """Decorator to register a function as a hook handler.
26
+
27
+ Args:
28
+ hook_type: Type of hook to register for.
29
+ priority: Execution priority.
30
+ conditions: Conditions for executing.
31
+ plugin_id: ID of the registering plugin.
32
+ auto_register: Whether to auto-register with global manager.
33
+
34
+ Returns:
35
+ Decorator function.
36
+
37
+ Example:
38
+ @hook(HookType.BEFORE_VALIDATION)
39
+ def my_handler(context: HookContext) -> HookResult | None:
40
+ # Process the hook
41
+ return HookResult(success=True)
42
+ """
43
+ if isinstance(hook_type, str):
44
+ hook_type = HookType(hook_type)
45
+
46
+ def decorator(func: F) -> F:
47
+ # Store hook metadata on the function
48
+ func._hook_type = hook_type # type: ignore
49
+ func._hook_priority = priority # type: ignore
50
+ func._hook_conditions = conditions or {} # type: ignore
51
+ func._hook_plugin_id = plugin_id # type: ignore
52
+
53
+ if auto_register:
54
+ hook_manager.register(
55
+ hook_type=hook_type,
56
+ handler=func,
57
+ plugin_id=plugin_id,
58
+ priority=priority,
59
+ conditions=conditions,
60
+ handler_name=func.__name__,
61
+ )
62
+
63
+ return func
64
+
65
+ return decorator
66
+
67
+
68
+ def before_validation(
69
+ priority: HookPriority | int = HookPriority.NORMAL,
70
+ conditions: dict[str, Any] | None = None,
71
+ plugin_id: str | None = None,
72
+ ) -> Callable[[F], F]:
73
+ """Decorator for before_validation hooks.
74
+
75
+ The handler receives:
76
+ - context.data["source_id"]: Data source ID
77
+ - context.data["validators"]: List of validators to run
78
+ - context.data["config"]: Validation configuration
79
+
80
+ The handler can:
81
+ - Modify validators list via context.modify("validators", [...])
82
+ - Cancel validation via context.cancel()
83
+
84
+ Example:
85
+ @before_validation()
86
+ def add_custom_validator(context: HookContext) -> HookResult | None:
87
+ validators = context.get("validators", [])
88
+ validators.append("my_custom_validator")
89
+ context.modify("validators", validators)
90
+ return HookResult(success=True, data={"added": "my_custom_validator"})
91
+ """
92
+ return hook(
93
+ HookType.BEFORE_VALIDATION,
94
+ priority=priority,
95
+ conditions=conditions,
96
+ plugin_id=plugin_id,
97
+ )
98
+
99
+
100
+ def after_validation(
101
+ priority: HookPriority | int = HookPriority.NORMAL,
102
+ conditions: dict[str, Any] | None = None,
103
+ plugin_id: str | None = None,
104
+ ) -> Callable[[F], F]:
105
+ """Decorator for after_validation hooks.
106
+
107
+ The handler receives:
108
+ - context.data["source_id"]: Data source ID
109
+ - context.data["result"]: Validation result
110
+ - context.data["issues"]: List of validation issues
111
+ - context.data["execution_time_ms"]: Execution time
112
+
113
+ Example:
114
+ @after_validation()
115
+ def log_validation_result(context: HookContext) -> HookResult | None:
116
+ issues = context.get("issues", [])
117
+ if issues:
118
+ print(f"Found {len(issues)} issues")
119
+ return HookResult(success=True)
120
+ """
121
+ return hook(
122
+ HookType.AFTER_VALIDATION,
123
+ priority=priority,
124
+ conditions=conditions,
125
+ plugin_id=plugin_id,
126
+ )
127
+
128
+
129
+ def on_issue_found(
130
+ priority: HookPriority | int = HookPriority.NORMAL,
131
+ conditions: dict[str, Any] | None = None,
132
+ plugin_id: str | None = None,
133
+ ) -> Callable[[F], F]:
134
+ """Decorator for on_issue_found hooks.
135
+
136
+ The handler receives:
137
+ - context.data["issue"]: The validation issue
138
+ - context.data["validator"]: The validator that found it
139
+ - context.data["column"]: Column name (if applicable)
140
+ - context.data["row_index"]: Row index (if applicable)
141
+
142
+ The handler can:
143
+ - Modify issue severity via context.modify("issue", {...})
144
+ - Suppress issue via context.modify("suppress", True)
145
+
146
+ Example:
147
+ @on_issue_found()
148
+ def filter_known_issues(context: HookContext) -> HookResult | None:
149
+ issue = context.get("issue", {})
150
+ if "known_pattern" in issue.get("message", ""):
151
+ context.modify("suppress", True)
152
+ return HookResult(success=True)
153
+ """
154
+ return hook(
155
+ HookType.ON_ISSUE_FOUND,
156
+ priority=priority,
157
+ conditions=conditions,
158
+ plugin_id=plugin_id,
159
+ )
160
+
161
+
162
+ def before_profile(
163
+ priority: HookPriority | int = HookPriority.NORMAL,
164
+ conditions: dict[str, Any] | None = None,
165
+ plugin_id: str | None = None,
166
+ ) -> Callable[[F], F]:
167
+ """Decorator for before_profile hooks.
168
+
169
+ The handler receives:
170
+ - context.data["source_id"]: Data source ID
171
+ - context.data["config"]: Profiling configuration
172
+ - context.data["columns"]: Columns to profile
173
+
174
+ Example:
175
+ @before_profile()
176
+ def configure_profiling(context: HookContext) -> HookResult | None:
177
+ config = context.get("config", {})
178
+ config["sample_size"] = 10000
179
+ context.modify("config", config)
180
+ return HookResult(success=True)
181
+ """
182
+ return hook(
183
+ HookType.BEFORE_PROFILE,
184
+ priority=priority,
185
+ conditions=conditions,
186
+ plugin_id=plugin_id,
187
+ )
188
+
189
+
190
+ def after_profile(
191
+ priority: HookPriority | int = HookPriority.NORMAL,
192
+ conditions: dict[str, Any] | None = None,
193
+ plugin_id: str | None = None,
194
+ ) -> Callable[[F], F]:
195
+ """Decorator for after_profile hooks.
196
+
197
+ The handler receives:
198
+ - context.data["source_id"]: Data source ID
199
+ - context.data["profile"]: Profile result
200
+ - context.data["execution_time_ms"]: Execution time
201
+
202
+ Example:
203
+ @after_profile()
204
+ def analyze_profile(context: HookContext) -> HookResult | None:
205
+ profile = context.get("profile", {})
206
+ # Perform additional analysis
207
+ return HookResult(success=True, data={"analysis": {...}})
208
+ """
209
+ return hook(
210
+ HookType.AFTER_PROFILE,
211
+ priority=priority,
212
+ conditions=conditions,
213
+ plugin_id=plugin_id,
214
+ )
215
+
216
+
217
+ def on_report_generate(
218
+ priority: HookPriority | int = HookPriority.NORMAL,
219
+ conditions: dict[str, Any] | None = None,
220
+ plugin_id: str | None = None,
221
+ ) -> Callable[[F], F]:
222
+ """Decorator for on_report_generate hooks.
223
+
224
+ The handler receives:
225
+ - context.data["report"]: Report data
226
+ - context.data["format"]: Output format (html, pdf, json, etc.)
227
+ - context.data["config"]: Report configuration
228
+
229
+ The handler can:
230
+ - Add sections via context.modify("sections", [...])
231
+ - Modify report data via context.modify("report", {...})
232
+
233
+ Example:
234
+ @on_report_generate()
235
+ def add_summary(context: HookContext) -> HookResult | None:
236
+ report = context.get("report", {})
237
+ report["custom_summary"] = "Generated by plugin"
238
+ context.modify("report", report)
239
+ return HookResult(success=True)
240
+ """
241
+ return hook(
242
+ HookType.ON_REPORT_GENERATE,
243
+ priority=priority,
244
+ conditions=conditions,
245
+ plugin_id=plugin_id,
246
+ )
247
+
248
+
249
+ def on_error(
250
+ priority: HookPriority | int = HookPriority.NORMAL,
251
+ conditions: dict[str, Any] | None = None,
252
+ plugin_id: str | None = None,
253
+ ) -> Callable[[F], F]:
254
+ """Decorator for on_error hooks.
255
+
256
+ The handler receives:
257
+ - context.data["error"]: The exception
258
+ - context.data["error_type"]: Exception type name
259
+ - context.data["context"]: Error context (operation, source, etc.)
260
+ - context.data["traceback"]: Traceback string
261
+
262
+ The handler can:
263
+ - Suppress error via context.modify("suppress", True)
264
+ - Provide fallback via context.modify("fallback", value)
265
+ - Request retry via context.modify("retry", True)
266
+
267
+ Example:
268
+ @on_error()
269
+ def handle_connection_error(context: HookContext) -> HookResult | None:
270
+ error_type = context.get("error_type")
271
+ if error_type == "ConnectionError":
272
+ context.modify("retry", True)
273
+ return HookResult(success=True, data={"action": "retry"})
274
+ return None
275
+ """
276
+ return hook(
277
+ HookType.ON_ERROR,
278
+ priority=priority,
279
+ conditions=conditions,
280
+ plugin_id=plugin_id,
281
+ )
282
+
283
+
284
+ class HookRegistrar:
285
+ """Helper class for registering multiple hooks from a plugin.
286
+
287
+ Example:
288
+ class MyPlugin:
289
+ def __init__(self):
290
+ self.hooks = HookRegistrar("my-plugin")
291
+
292
+ def register_hooks(self):
293
+ self.hooks.register(
294
+ HookType.BEFORE_VALIDATION,
295
+ self.before_validation,
296
+ )
297
+ self.hooks.register(
298
+ HookType.AFTER_VALIDATION,
299
+ self.after_validation,
300
+ )
301
+
302
+ def unregister_hooks(self):
303
+ self.hooks.unregister_all()
304
+ """
305
+
306
+ def __init__(self, plugin_id: str) -> None:
307
+ """Initialize the registrar.
308
+
309
+ Args:
310
+ plugin_id: Plugin ID for all registrations.
311
+ """
312
+ self.plugin_id = plugin_id
313
+ self._registration_ids: list[str] = []
314
+
315
+ def register(
316
+ self,
317
+ hook_type: HookType | str,
318
+ handler: Callable[[HookContext], HookResult | None],
319
+ priority: HookPriority | int = HookPriority.NORMAL,
320
+ conditions: dict[str, Any] | None = None,
321
+ ) -> str:
322
+ """Register a hook handler.
323
+
324
+ Args:
325
+ hook_type: Type of hook.
326
+ handler: Handler function.
327
+ priority: Execution priority.
328
+ conditions: Conditions for executing.
329
+
330
+ Returns:
331
+ Registration ID.
332
+ """
333
+ reg_id = hook_manager.register(
334
+ hook_type=hook_type,
335
+ handler=handler,
336
+ plugin_id=self.plugin_id,
337
+ priority=priority,
338
+ conditions=conditions,
339
+ )
340
+ self._registration_ids.append(reg_id)
341
+ return reg_id
342
+
343
+ def unregister(self, registration_id: str) -> bool:
344
+ """Unregister a specific handler.
345
+
346
+ Args:
347
+ registration_id: Registration ID.
348
+
349
+ Returns:
350
+ True if successful.
351
+ """
352
+ if registration_id in self._registration_ids:
353
+ self._registration_ids.remove(registration_id)
354
+ return hook_manager.unregister(registration_id)
355
+
356
+ def unregister_all(self) -> int:
357
+ """Unregister all handlers for this plugin.
358
+
359
+ Returns:
360
+ Number of handlers unregistered.
361
+ """
362
+ count = 0
363
+ for reg_id in list(self._registration_ids):
364
+ if hook_manager.unregister(reg_id):
365
+ count += 1
366
+ self._registration_ids.clear()
367
+ return count