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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -22
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
- truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
- truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {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
|