truthound-dashboard 1.4.3__py3-none-any.whl → 1.5.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 +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +437 -10
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +11 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""Reporter factory with flexible backend selection.
|
|
2
|
+
|
|
3
|
+
This module provides a unified factory for creating reporters from
|
|
4
|
+
various backends (dashboard-native, truthound, custom).
|
|
5
|
+
|
|
6
|
+
The factory supports:
|
|
7
|
+
1. Multiple backend priority (truthound first, then fallback)
|
|
8
|
+
2. Dynamic reporter registration
|
|
9
|
+
3. Lazy loading of backends
|
|
10
|
+
4. Configuration-based reporter creation
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
from truthound_dashboard.core.reporters.factory import (
|
|
14
|
+
get_reporter_factory,
|
|
15
|
+
ReporterFactory,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
factory = get_reporter_factory()
|
|
19
|
+
|
|
20
|
+
# Get reporter by format
|
|
21
|
+
reporter = factory.get_reporter("json")
|
|
22
|
+
|
|
23
|
+
# Generate report
|
|
24
|
+
output = await reporter.generate(data)
|
|
25
|
+
|
|
26
|
+
# Check available formats
|
|
27
|
+
formats = factory.get_available_formats()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
from typing import Any, Callable, TypeVar
|
|
34
|
+
|
|
35
|
+
from .interfaces import (
|
|
36
|
+
BaseReporter,
|
|
37
|
+
ReportData,
|
|
38
|
+
ReporterConfig,
|
|
39
|
+
ReporterProtocol,
|
|
40
|
+
ReportFormatType,
|
|
41
|
+
ReportOutput,
|
|
42
|
+
ReportThemeType,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
ReporterT = TypeVar("ReporterT", bound=ReporterProtocol)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ReporterFactory:
|
|
51
|
+
"""Factory for creating reporter instances.
|
|
52
|
+
|
|
53
|
+
This factory supports multiple backends with priority ordering:
|
|
54
|
+
1. Explicitly registered reporters (highest priority)
|
|
55
|
+
2. Truthound reporters (if available)
|
|
56
|
+
3. Dashboard built-in reporters (fallback)
|
|
57
|
+
|
|
58
|
+
The factory uses lazy loading to avoid importing unnecessary
|
|
59
|
+
dependencies until they're actually needed.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
backends: List of backend names in priority order.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
use_truthound: bool = True,
|
|
68
|
+
default_locale: str = "en",
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Initialize factory.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
use_truthound: Whether to try truthound reporters first.
|
|
74
|
+
default_locale: Default locale for i18n.
|
|
75
|
+
"""
|
|
76
|
+
self._use_truthound = use_truthound
|
|
77
|
+
self._default_locale = default_locale
|
|
78
|
+
|
|
79
|
+
# Registered reporter classes (format -> class)
|
|
80
|
+
self._registered: dict[ReportFormatType, type[ReporterProtocol]] = {}
|
|
81
|
+
|
|
82
|
+
# Registered reporter instances (format -> instance)
|
|
83
|
+
self._instances: dict[str, ReporterProtocol] = {}
|
|
84
|
+
|
|
85
|
+
# Factory functions for custom reporters
|
|
86
|
+
self._factories: dict[ReportFormatType, Callable[..., ReporterProtocol]] = {}
|
|
87
|
+
|
|
88
|
+
# Cached truthound availability
|
|
89
|
+
self._truthound_available: bool | None = None
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def backends(self) -> list[str]:
|
|
93
|
+
"""Get list of backend names in priority order."""
|
|
94
|
+
backends = ["registered"]
|
|
95
|
+
if self._use_truthound and self._is_truthound_available():
|
|
96
|
+
backends.append("truthound")
|
|
97
|
+
backends.append("builtin")
|
|
98
|
+
return backends
|
|
99
|
+
|
|
100
|
+
def _is_truthound_available(self) -> bool:
|
|
101
|
+
"""Check if truthound is available (cached)."""
|
|
102
|
+
if self._truthound_available is None:
|
|
103
|
+
from .adapters import is_truthound_available
|
|
104
|
+
|
|
105
|
+
self._truthound_available = is_truthound_available()
|
|
106
|
+
return self._truthound_available
|
|
107
|
+
|
|
108
|
+
def register(
|
|
109
|
+
self,
|
|
110
|
+
format_type: ReportFormatType | str,
|
|
111
|
+
reporter_class: type[ReporterProtocol],
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Register a reporter class for a format.
|
|
114
|
+
|
|
115
|
+
Registered reporters have highest priority and will be used
|
|
116
|
+
instead of truthound or built-in reporters.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
format_type: Report format to register for.
|
|
120
|
+
reporter_class: Reporter class to use.
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(format_type, str):
|
|
123
|
+
format_type = ReportFormatType.from_string(format_type)
|
|
124
|
+
|
|
125
|
+
self._registered[format_type] = reporter_class
|
|
126
|
+
logger.debug(f"Registered reporter {reporter_class.__name__} for {format_type.value}")
|
|
127
|
+
|
|
128
|
+
def register_factory(
|
|
129
|
+
self,
|
|
130
|
+
format_type: ReportFormatType | str,
|
|
131
|
+
factory_func: Callable[..., ReporterProtocol],
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Register a factory function for creating reporters.
|
|
134
|
+
|
|
135
|
+
This is useful for reporters that need complex initialization
|
|
136
|
+
or dependency injection.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
format_type: Report format.
|
|
140
|
+
factory_func: Factory function that returns a reporter.
|
|
141
|
+
"""
|
|
142
|
+
if isinstance(format_type, str):
|
|
143
|
+
format_type = ReportFormatType.from_string(format_type)
|
|
144
|
+
|
|
145
|
+
self._factories[format_type] = factory_func
|
|
146
|
+
logger.debug(f"Registered factory for {format_type.value}")
|
|
147
|
+
|
|
148
|
+
def register_instance(
|
|
149
|
+
self,
|
|
150
|
+
format_name: str,
|
|
151
|
+
reporter: ReporterProtocol,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Register a specific reporter instance.
|
|
154
|
+
|
|
155
|
+
This is useful for sharing pre-configured reporter instances.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
format_name: Format name (used as key).
|
|
159
|
+
reporter: Reporter instance.
|
|
160
|
+
"""
|
|
161
|
+
self._instances[format_name.lower()] = reporter
|
|
162
|
+
logger.debug(f"Registered instance for {format_name}")
|
|
163
|
+
|
|
164
|
+
def unregister(self, format_type: ReportFormatType | str) -> None:
|
|
165
|
+
"""Unregister a reporter for a format.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
format_type: Format to unregister.
|
|
169
|
+
"""
|
|
170
|
+
if isinstance(format_type, str):
|
|
171
|
+
format_type = ReportFormatType.from_string(format_type)
|
|
172
|
+
|
|
173
|
+
self._registered.pop(format_type, None)
|
|
174
|
+
self._factories.pop(format_type, None)
|
|
175
|
+
self._instances.pop(format_type.value, None)
|
|
176
|
+
|
|
177
|
+
def get_reporter(
|
|
178
|
+
self,
|
|
179
|
+
format: ReportFormatType | str,
|
|
180
|
+
config: ReporterConfig | None = None,
|
|
181
|
+
locale: str | None = None,
|
|
182
|
+
prefer_truthound: bool = False,
|
|
183
|
+
**kwargs: Any,
|
|
184
|
+
) -> ReporterProtocol:
|
|
185
|
+
"""Get a reporter for the specified format.
|
|
186
|
+
|
|
187
|
+
The factory tries backends in this order:
|
|
188
|
+
1. Registered instance (exact match by format name)
|
|
189
|
+
2. Registered factory function
|
|
190
|
+
3. Registered class
|
|
191
|
+
4. Built-in dashboard reporter (default)
|
|
192
|
+
5. Truthound reporter (if prefer_truthound=True or format not in builtin)
|
|
193
|
+
|
|
194
|
+
Note: By default, built-in reporters are preferred because they work
|
|
195
|
+
reliably with our ReportData format. Truthound reporters require
|
|
196
|
+
a real truthound Report/ValidationResult object for full compatibility.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
format: Report format (enum or string).
|
|
200
|
+
config: Optional configuration.
|
|
201
|
+
locale: Locale override (uses default if not specified).
|
|
202
|
+
prefer_truthound: If True, try truthound reporters before built-in.
|
|
203
|
+
**kwargs: Additional arguments passed to reporter constructor.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Reporter instance.
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
ValueError: If no reporter is available for the format.
|
|
210
|
+
"""
|
|
211
|
+
# Normalize format
|
|
212
|
+
if isinstance(format, str):
|
|
213
|
+
format_str = format.lower()
|
|
214
|
+
try:
|
|
215
|
+
format_type = ReportFormatType.from_string(format_str)
|
|
216
|
+
except ValueError:
|
|
217
|
+
format_type = None
|
|
218
|
+
else:
|
|
219
|
+
format_type = format
|
|
220
|
+
format_str = format.value
|
|
221
|
+
|
|
222
|
+
effective_locale = locale or self._default_locale
|
|
223
|
+
|
|
224
|
+
# 1. Check for registered instance
|
|
225
|
+
if format_str in self._instances:
|
|
226
|
+
return self._instances[format_str]
|
|
227
|
+
|
|
228
|
+
# 2. Check for factory function
|
|
229
|
+
if format_type and format_type in self._factories:
|
|
230
|
+
return self._factories[format_type](
|
|
231
|
+
config=config,
|
|
232
|
+
locale=effective_locale,
|
|
233
|
+
**kwargs,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# 3. Check for registered class
|
|
237
|
+
if format_type and format_type in self._registered:
|
|
238
|
+
reporter_class = self._registered[format_type]
|
|
239
|
+
return reporter_class(**kwargs)
|
|
240
|
+
|
|
241
|
+
# 4 & 5. Try built-in or truthound based on preference
|
|
242
|
+
if prefer_truthound and self._use_truthound and self._is_truthound_available():
|
|
243
|
+
reporter = self._create_truthound_reporter(
|
|
244
|
+
format_str,
|
|
245
|
+
locale=effective_locale,
|
|
246
|
+
**kwargs,
|
|
247
|
+
)
|
|
248
|
+
if reporter:
|
|
249
|
+
return reporter
|
|
250
|
+
|
|
251
|
+
# 4. Try built-in reporter first (default preference)
|
|
252
|
+
if format_type:
|
|
253
|
+
reporter = self._create_builtin_reporter(format_type, locale=effective_locale)
|
|
254
|
+
if reporter:
|
|
255
|
+
return reporter
|
|
256
|
+
|
|
257
|
+
# 5. Try truthound reporter as fallback (for formats not in builtin)
|
|
258
|
+
if self._use_truthound and self._is_truthound_available():
|
|
259
|
+
reporter = self._create_truthound_reporter(
|
|
260
|
+
format_str,
|
|
261
|
+
locale=effective_locale,
|
|
262
|
+
**kwargs,
|
|
263
|
+
)
|
|
264
|
+
if reporter:
|
|
265
|
+
return reporter
|
|
266
|
+
|
|
267
|
+
raise ValueError(
|
|
268
|
+
f"No reporter available for format: {format_str}. "
|
|
269
|
+
f"Available formats: {self.get_available_formats()}"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _create_truthound_reporter(
|
|
273
|
+
self,
|
|
274
|
+
format_name: str,
|
|
275
|
+
locale: str,
|
|
276
|
+
**kwargs: Any,
|
|
277
|
+
) -> ReporterProtocol | None:
|
|
278
|
+
"""Create a truthound reporter if available."""
|
|
279
|
+
from .adapters import create_truthound_reporter
|
|
280
|
+
|
|
281
|
+
return create_truthound_reporter(format_name, locale=locale, **kwargs)
|
|
282
|
+
|
|
283
|
+
def _create_builtin_reporter(
|
|
284
|
+
self,
|
|
285
|
+
format_type: ReportFormatType,
|
|
286
|
+
locale: str = "en",
|
|
287
|
+
) -> ReporterProtocol | None:
|
|
288
|
+
"""Create a built-in dashboard reporter."""
|
|
289
|
+
# Lazy import to avoid circular imports
|
|
290
|
+
try:
|
|
291
|
+
if format_type == ReportFormatType.JSON:
|
|
292
|
+
from .builtin.json_reporter import BuiltinJSONReporter
|
|
293
|
+
|
|
294
|
+
return BuiltinJSONReporter(locale=locale)
|
|
295
|
+
|
|
296
|
+
elif format_type == ReportFormatType.HTML:
|
|
297
|
+
from .builtin.html_reporter import BuiltinHTMLReporter
|
|
298
|
+
|
|
299
|
+
return BuiltinHTMLReporter(locale=locale)
|
|
300
|
+
|
|
301
|
+
elif format_type == ReportFormatType.CSV:
|
|
302
|
+
from .builtin.csv_reporter import BuiltinCSVReporter
|
|
303
|
+
|
|
304
|
+
return BuiltinCSVReporter()
|
|
305
|
+
|
|
306
|
+
except ImportError as e:
|
|
307
|
+
logger.warning(f"Built-in reporter for {format_type.value} not available: {e}")
|
|
308
|
+
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
def get_available_formats(self) -> list[str]:
|
|
312
|
+
"""Get list of available format names.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
List of format name strings.
|
|
316
|
+
"""
|
|
317
|
+
formats = set()
|
|
318
|
+
|
|
319
|
+
# Add registered formats
|
|
320
|
+
for fmt in self._registered.keys():
|
|
321
|
+
formats.add(fmt.value)
|
|
322
|
+
for fmt in self._factories.keys():
|
|
323
|
+
formats.add(fmt.value)
|
|
324
|
+
for fmt in self._instances.keys():
|
|
325
|
+
formats.add(fmt)
|
|
326
|
+
|
|
327
|
+
# Add truthound formats
|
|
328
|
+
if self._use_truthound and self._is_truthound_available():
|
|
329
|
+
from .adapters import get_truthound_formats
|
|
330
|
+
|
|
331
|
+
formats.update(get_truthound_formats())
|
|
332
|
+
|
|
333
|
+
# Add built-in formats
|
|
334
|
+
builtin_formats = ["json", "html", "csv"]
|
|
335
|
+
formats.update(builtin_formats)
|
|
336
|
+
|
|
337
|
+
return sorted(formats)
|
|
338
|
+
|
|
339
|
+
def is_format_available(self, format: ReportFormatType | str) -> bool:
|
|
340
|
+
"""Check if a format is available.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
format: Format to check.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
True if the format is available.
|
|
347
|
+
"""
|
|
348
|
+
if isinstance(format, str):
|
|
349
|
+
format_str = format.lower()
|
|
350
|
+
else:
|
|
351
|
+
format_str = format.value
|
|
352
|
+
|
|
353
|
+
return format_str in self.get_available_formats()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# Global factory instance
|
|
357
|
+
_factory: ReporterFactory | None = None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def get_reporter_factory(
|
|
361
|
+
use_truthound: bool = True,
|
|
362
|
+
default_locale: str = "en",
|
|
363
|
+
) -> ReporterFactory:
|
|
364
|
+
"""Get the global reporter factory.
|
|
365
|
+
|
|
366
|
+
Creates the factory on first call with default settings.
|
|
367
|
+
Subsequent calls return the same instance.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
use_truthound: Whether to enable truthound backend (only on first call).
|
|
371
|
+
default_locale: Default locale (only on first call).
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Global ReporterFactory instance.
|
|
375
|
+
"""
|
|
376
|
+
global _factory
|
|
377
|
+
if _factory is None:
|
|
378
|
+
_factory = ReporterFactory(
|
|
379
|
+
use_truthound=use_truthound,
|
|
380
|
+
default_locale=default_locale,
|
|
381
|
+
)
|
|
382
|
+
return _factory
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def reset_factory() -> None:
|
|
386
|
+
"""Reset the global factory (for testing)."""
|
|
387
|
+
global _factory
|
|
388
|
+
_factory = None
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def register_reporter(
|
|
392
|
+
format_type: ReportFormatType | str,
|
|
393
|
+
) -> Callable[[type[ReporterT]], type[ReporterT]]:
|
|
394
|
+
"""Decorator to register a reporter class.
|
|
395
|
+
|
|
396
|
+
Example:
|
|
397
|
+
@register_reporter("custom")
|
|
398
|
+
class MyCustomReporter(BaseReporter):
|
|
399
|
+
...
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
def decorator(cls: type[ReporterT]) -> type[ReporterT]:
|
|
403
|
+
get_reporter_factory().register(format_type, cls)
|
|
404
|
+
return cls
|
|
405
|
+
|
|
406
|
+
return decorator
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Convenience functions that delegate to the global factory
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def get_reporter(
|
|
413
|
+
format: ReportFormatType | str,
|
|
414
|
+
config: ReporterConfig | None = None,
|
|
415
|
+
locale: str | None = None,
|
|
416
|
+
**kwargs: Any,
|
|
417
|
+
) -> ReporterProtocol:
|
|
418
|
+
"""Get a reporter for the specified format.
|
|
419
|
+
|
|
420
|
+
Convenience function that uses the global factory.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
format: Report format.
|
|
424
|
+
config: Optional configuration.
|
|
425
|
+
locale: Locale override.
|
|
426
|
+
**kwargs: Additional arguments.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Reporter instance.
|
|
430
|
+
"""
|
|
431
|
+
return get_reporter_factory().get_reporter(
|
|
432
|
+
format,
|
|
433
|
+
config=config,
|
|
434
|
+
locale=locale,
|
|
435
|
+
**kwargs,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def get_available_formats() -> list[str]:
|
|
440
|
+
"""Get list of available report formats.
|
|
441
|
+
|
|
442
|
+
Convenience function that uses the global factory.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
List of format name strings.
|
|
446
|
+
"""
|
|
447
|
+
return get_reporter_factory().get_available_formats()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def is_format_available(format: ReportFormatType | str) -> bool:
|
|
451
|
+
"""Check if a format is available.
|
|
452
|
+
|
|
453
|
+
Convenience function that uses the global factory.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
format: Format to check.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
True if available.
|
|
460
|
+
"""
|
|
461
|
+
return get_reporter_factory().is_format_available(format)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
async def generate_report(
|
|
465
|
+
data: ReportData,
|
|
466
|
+
format: ReportFormatType | str = ReportFormatType.HTML,
|
|
467
|
+
config: ReporterConfig | None = None,
|
|
468
|
+
locale: str | None = None,
|
|
469
|
+
**kwargs: Any,
|
|
470
|
+
) -> ReportOutput:
|
|
471
|
+
"""Generate a report using the appropriate reporter.
|
|
472
|
+
|
|
473
|
+
High-level convenience function for report generation.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
data: Report data.
|
|
477
|
+
format: Output format.
|
|
478
|
+
config: Reporter configuration.
|
|
479
|
+
locale: Locale for i18n.
|
|
480
|
+
**kwargs: Additional arguments passed to reporter.
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
ReportOutput with generated content.
|
|
484
|
+
|
|
485
|
+
Example:
|
|
486
|
+
data = ReportData.from_validation_model(validation)
|
|
487
|
+
output = await generate_report(data, format="html", locale="ko")
|
|
488
|
+
"""
|
|
489
|
+
reporter = get_reporter(format, config=config, locale=locale, **kwargs)
|
|
490
|
+
return await reporter.generate(data, config=config)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
async def generate_report_from_validation(
|
|
494
|
+
validation: Any,
|
|
495
|
+
format: ReportFormatType | str = ReportFormatType.HTML,
|
|
496
|
+
config: ReporterConfig | None = None,
|
|
497
|
+
locale: str | None = None,
|
|
498
|
+
**kwargs: Any,
|
|
499
|
+
) -> ReportOutput:
|
|
500
|
+
"""Generate a report from a Validation model.
|
|
501
|
+
|
|
502
|
+
Convenience function that handles the conversion from
|
|
503
|
+
Validation model to ReportData.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
validation: Validation model from database.
|
|
507
|
+
format: Output format.
|
|
508
|
+
config: Reporter configuration.
|
|
509
|
+
locale: Locale for i18n.
|
|
510
|
+
**kwargs: Additional arguments.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
ReportOutput with generated content.
|
|
514
|
+
|
|
515
|
+
Example:
|
|
516
|
+
validation = await service.get_validation(validation_id)
|
|
517
|
+
output = await generate_report_from_validation(
|
|
518
|
+
validation,
|
|
519
|
+
format="html",
|
|
520
|
+
locale="ko",
|
|
521
|
+
)
|
|
522
|
+
"""
|
|
523
|
+
from .adapters import ValidationModelAdapter
|
|
524
|
+
|
|
525
|
+
data = ValidationModelAdapter.to_report_data(validation)
|
|
526
|
+
return await generate_report(data, format=format, config=config, locale=locale, **kwargs)
|