truthound-dashboard 1.4.4__py3-none-any.whl → 1.5.1__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 +645 -23
- 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 +15 -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.1.dist-info/METADATA +312 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.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.4.dist-info/METADATA +0 -507
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""Backend factory for data quality backends.
|
|
2
|
+
|
|
3
|
+
This module provides a factory for creating and managing data quality
|
|
4
|
+
backends. It supports registration of custom backends and automatic
|
|
5
|
+
fallback when the primary backend is unavailable.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Lazy initialization of built-in backends
|
|
9
|
+
- Automatic fallback to mock backend when truthound unavailable
|
|
10
|
+
- Instance caching for efficiency
|
|
11
|
+
- Backend version detection
|
|
12
|
+
- Capability reporting for feature detection
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from .errors import BackendUnavailableError
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .base import BaseDataQualityBackend
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BackendFactory:
|
|
29
|
+
"""Factory for creating data quality backends.
|
|
30
|
+
|
|
31
|
+
The factory manages backend registration and instantiation with
|
|
32
|
+
support for automatic fallback when backends are unavailable.
|
|
33
|
+
|
|
34
|
+
Class Attributes:
|
|
35
|
+
_backends: Registry of backend classes by name.
|
|
36
|
+
_instances: Cached backend instances.
|
|
37
|
+
_default_backend: Name of the default backend.
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
# Get the default backend
|
|
41
|
+
backend = BackendFactory.get_backend()
|
|
42
|
+
|
|
43
|
+
# Get a specific backend
|
|
44
|
+
mock_backend = BackendFactory.get_backend("mock")
|
|
45
|
+
|
|
46
|
+
# Register a custom backend
|
|
47
|
+
BackendFactory.register("custom", MyCustomBackend)
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
_backends: dict[str, type["BaseDataQualityBackend"]] = {}
|
|
51
|
+
_instances: dict[str, "BaseDataQualityBackend"] = {}
|
|
52
|
+
_default_backend: str = "truthound"
|
|
53
|
+
_fallback_backend: str = "mock"
|
|
54
|
+
_initialized: bool = False
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _ensure_initialized(cls) -> None:
|
|
58
|
+
"""Ensure built-in backends are registered."""
|
|
59
|
+
if cls._initialized:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Import and register built-in backends
|
|
63
|
+
from .mock_backend import MockBackend
|
|
64
|
+
from .truthound_backend import TruthoundBackend
|
|
65
|
+
|
|
66
|
+
cls._backends["truthound"] = TruthoundBackend
|
|
67
|
+
cls._backends["mock"] = MockBackend
|
|
68
|
+
cls._initialized = True
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def register(
|
|
72
|
+
cls,
|
|
73
|
+
name: str,
|
|
74
|
+
backend_class: type["BaseDataQualityBackend"],
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Register a backend class.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name: Backend name for lookup.
|
|
80
|
+
backend_class: Backend class to register.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
class MyBackend(BaseDataQualityBackend):
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
BackendFactory.register("my-backend", MyBackend)
|
|
87
|
+
"""
|
|
88
|
+
cls._ensure_initialized()
|
|
89
|
+
cls._backends[name] = backend_class
|
|
90
|
+
logger.debug(f"Registered backend: {name}")
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def unregister(cls, name: str) -> None:
|
|
94
|
+
"""Unregister a backend.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
name: Backend name to unregister.
|
|
98
|
+
"""
|
|
99
|
+
cls._ensure_initialized()
|
|
100
|
+
if name in cls._backends:
|
|
101
|
+
del cls._backends[name]
|
|
102
|
+
if name in cls._instances:
|
|
103
|
+
cls._instances[name].shutdown()
|
|
104
|
+
del cls._instances[name]
|
|
105
|
+
logger.debug(f"Unregistered backend: {name}")
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def get_backend(
|
|
109
|
+
cls,
|
|
110
|
+
name: str | None = None,
|
|
111
|
+
*,
|
|
112
|
+
fallback: bool = True,
|
|
113
|
+
max_workers: int = 4,
|
|
114
|
+
) -> "BaseDataQualityBackend":
|
|
115
|
+
"""Get a backend instance.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name: Backend name. If None, uses default backend.
|
|
119
|
+
fallback: If True, fallback to mock when primary unavailable.
|
|
120
|
+
max_workers: Maximum worker threads for the backend.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Backend instance.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
BackendUnavailableError: If backend not available and no fallback.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
# Get default backend with fallback
|
|
130
|
+
backend = BackendFactory.get_backend()
|
|
131
|
+
|
|
132
|
+
# Get specific backend without fallback
|
|
133
|
+
backend = BackendFactory.get_backend("truthound", fallback=False)
|
|
134
|
+
"""
|
|
135
|
+
cls._ensure_initialized()
|
|
136
|
+
|
|
137
|
+
backend_name = name or cls._default_backend
|
|
138
|
+
|
|
139
|
+
# Check if we have a cached instance
|
|
140
|
+
if backend_name in cls._instances:
|
|
141
|
+
instance = cls._instances[backend_name]
|
|
142
|
+
if instance.is_available():
|
|
143
|
+
return instance
|
|
144
|
+
else:
|
|
145
|
+
# Instance no longer available, remove from cache
|
|
146
|
+
del cls._instances[backend_name]
|
|
147
|
+
|
|
148
|
+
# Try to create the requested backend
|
|
149
|
+
if backend_name in cls._backends:
|
|
150
|
+
try:
|
|
151
|
+
instance = cls._backends[backend_name](max_workers=max_workers)
|
|
152
|
+
if instance.is_available():
|
|
153
|
+
cls._instances[backend_name] = instance
|
|
154
|
+
logger.info(f"Using backend: {backend_name}")
|
|
155
|
+
return instance
|
|
156
|
+
else:
|
|
157
|
+
logger.warning(f"Backend '{backend_name}' not available")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.warning(f"Failed to create backend '{backend_name}': {e}")
|
|
160
|
+
|
|
161
|
+
# Try fallback if enabled
|
|
162
|
+
if fallback and cls._fallback_backend in cls._backends:
|
|
163
|
+
fallback_name = cls._fallback_backend
|
|
164
|
+
if fallback_name not in cls._instances:
|
|
165
|
+
instance = cls._backends[fallback_name](max_workers=max_workers)
|
|
166
|
+
cls._instances[fallback_name] = instance
|
|
167
|
+
logger.info(f"Using fallback backend: {fallback_name}")
|
|
168
|
+
return cls._instances[fallback_name]
|
|
169
|
+
|
|
170
|
+
raise BackendUnavailableError(
|
|
171
|
+
backend_name,
|
|
172
|
+
f"Backend not available and no fallback. "
|
|
173
|
+
f"Available backends: {list(cls._backends.keys())}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def get_available_backends(cls) -> list[str]:
|
|
178
|
+
"""Get list of available backend names.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
List of backend names that are currently available.
|
|
182
|
+
"""
|
|
183
|
+
cls._ensure_initialized()
|
|
184
|
+
|
|
185
|
+
available = []
|
|
186
|
+
for name, backend_class in cls._backends.items():
|
|
187
|
+
try:
|
|
188
|
+
instance = backend_class()
|
|
189
|
+
if instance.is_available():
|
|
190
|
+
available.append(name)
|
|
191
|
+
instance.shutdown()
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
return available
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def set_default_backend(cls, name: str) -> None:
|
|
198
|
+
"""Set the default backend.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
name: Backend name to use as default.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
ValueError: If backend is not registered.
|
|
205
|
+
"""
|
|
206
|
+
cls._ensure_initialized()
|
|
207
|
+
|
|
208
|
+
if name not in cls._backends:
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Backend '{name}' not registered. "
|
|
211
|
+
f"Available: {list(cls._backends.keys())}"
|
|
212
|
+
)
|
|
213
|
+
cls._default_backend = name
|
|
214
|
+
logger.info(f"Default backend set to: {name}")
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def set_fallback_backend(cls, name: str | None) -> None:
|
|
218
|
+
"""Set the fallback backend.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
name: Backend name for fallback, or None to disable fallback.
|
|
222
|
+
"""
|
|
223
|
+
cls._ensure_initialized()
|
|
224
|
+
|
|
225
|
+
if name and name not in cls._backends:
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"Backend '{name}' not registered. "
|
|
228
|
+
f"Available: {list(cls._backends.keys())}"
|
|
229
|
+
)
|
|
230
|
+
cls._fallback_backend = name or ""
|
|
231
|
+
logger.info(f"Fallback backend set to: {name or 'disabled'}")
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def reset(cls) -> None:
|
|
235
|
+
"""Reset factory state (for testing).
|
|
236
|
+
|
|
237
|
+
Shuts down all cached instances and clears registration.
|
|
238
|
+
"""
|
|
239
|
+
for instance in cls._instances.values():
|
|
240
|
+
instance.shutdown()
|
|
241
|
+
cls._instances.clear()
|
|
242
|
+
cls._backends.clear()
|
|
243
|
+
cls._initialized = False
|
|
244
|
+
cls._default_backend = "truthound"
|
|
245
|
+
cls._fallback_backend = "mock"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# =============================================================================
|
|
249
|
+
# Module-level convenience functions
|
|
250
|
+
# =============================================================================
|
|
251
|
+
|
|
252
|
+
_default_backend: "BaseDataQualityBackend | None" = None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def get_backend(
|
|
256
|
+
name: str | None = None,
|
|
257
|
+
*,
|
|
258
|
+
fallback: bool = True,
|
|
259
|
+
) -> "BaseDataQualityBackend":
|
|
260
|
+
"""Get a backend instance.
|
|
261
|
+
|
|
262
|
+
Convenience function that uses BackendFactory.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
name: Backend name. If None, uses default backend.
|
|
266
|
+
fallback: If True, fallback to mock when primary unavailable.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Backend instance.
|
|
270
|
+
"""
|
|
271
|
+
global _default_backend
|
|
272
|
+
|
|
273
|
+
if name is None and _default_backend is not None:
|
|
274
|
+
return _default_backend
|
|
275
|
+
|
|
276
|
+
backend = BackendFactory.get_backend(name, fallback=fallback)
|
|
277
|
+
|
|
278
|
+
if name is None:
|
|
279
|
+
_default_backend = backend
|
|
280
|
+
|
|
281
|
+
return backend
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def reset_backend() -> None:
|
|
285
|
+
"""Reset the cached default backend (for testing)."""
|
|
286
|
+
global _default_backend
|
|
287
|
+
_default_backend = None
|
|
288
|
+
BackendFactory.reset()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# =============================================================================
|
|
292
|
+
# Truthound Version and Capability Detection
|
|
293
|
+
# =============================================================================
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def get_truthound_version() -> str | None:
|
|
297
|
+
"""Get the installed truthound version.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Version string or None if truthound not installed.
|
|
301
|
+
"""
|
|
302
|
+
try:
|
|
303
|
+
import truthound
|
|
304
|
+
|
|
305
|
+
return getattr(truthound, "__version__", None)
|
|
306
|
+
except ImportError:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def get_backend_capabilities() -> dict[str, bool]:
|
|
311
|
+
"""Get capabilities of the current backend.
|
|
312
|
+
|
|
313
|
+
Returns a dictionary indicating which features are available
|
|
314
|
+
in the current backend configuration.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dictionary mapping capability names to availability.
|
|
318
|
+
"""
|
|
319
|
+
backend = get_backend(fallback=True)
|
|
320
|
+
capabilities = {
|
|
321
|
+
# Core validation
|
|
322
|
+
"check": True, # Always available
|
|
323
|
+
"learn": True, # Schema learning
|
|
324
|
+
"profile": True, # Data profiling
|
|
325
|
+
"compare": True, # Drift detection
|
|
326
|
+
"scan": True, # PII scanning
|
|
327
|
+
"mask": True, # Data masking
|
|
328
|
+
# Advanced features
|
|
329
|
+
"generate_suite": _has_method(backend, "generate_suite"),
|
|
330
|
+
"parallel_execution": True, # Thread pool executor
|
|
331
|
+
"async_execution": True, # Async/await support
|
|
332
|
+
# Checkpoint features
|
|
333
|
+
"checkpoint": _is_checkpoint_available(),
|
|
334
|
+
"checkpoint_actions": _is_checkpoint_available(),
|
|
335
|
+
"checkpoint_triggers": _is_checkpoint_available(),
|
|
336
|
+
"checkpoint_ci_reporters": _is_ci_reporters_available(),
|
|
337
|
+
# Reporter features
|
|
338
|
+
"reporters": _is_reporters_available(),
|
|
339
|
+
"ci_reporters": _is_ci_reporters_available(),
|
|
340
|
+
# Version-specific features
|
|
341
|
+
"truthound_2x_api": _is_truthound_2x_api(),
|
|
342
|
+
}
|
|
343
|
+
return capabilities
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _has_method(obj: object, method_name: str) -> bool:
|
|
347
|
+
"""Check if an object has a callable method."""
|
|
348
|
+
attr = getattr(obj, method_name, None)
|
|
349
|
+
return callable(attr)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _is_checkpoint_available() -> bool:
|
|
353
|
+
"""Check if truthound checkpoint module is available."""
|
|
354
|
+
try:
|
|
355
|
+
from truthound.checkpoint import Checkpoint # noqa: F401
|
|
356
|
+
|
|
357
|
+
return True
|
|
358
|
+
except ImportError:
|
|
359
|
+
return False
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _is_reporters_available() -> bool:
|
|
363
|
+
"""Check if truthound reporters module is available."""
|
|
364
|
+
try:
|
|
365
|
+
from truthound.reporters import get_reporter # noqa: F401
|
|
366
|
+
|
|
367
|
+
return True
|
|
368
|
+
except ImportError:
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _is_ci_reporters_available() -> bool:
|
|
373
|
+
"""Check if truthound CI reporters are available."""
|
|
374
|
+
try:
|
|
375
|
+
from truthound.checkpoint.ci import detect_ci_platform # noqa: F401
|
|
376
|
+
|
|
377
|
+
return True
|
|
378
|
+
except ImportError:
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _is_truthound_2x_api() -> bool:
|
|
383
|
+
"""Check if truthound 2.x API is available (explicit imports)."""
|
|
384
|
+
try:
|
|
385
|
+
# Check for new explicit import pattern
|
|
386
|
+
from truthound.datasources.sql.postgresql import ( # noqa: F401
|
|
387
|
+
PostgreSQLDataSource,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return True
|
|
391
|
+
except ImportError:
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_backend_info() -> dict[str, any]:
|
|
396
|
+
"""Get comprehensive information about the current backend.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Dictionary with backend information including:
|
|
400
|
+
- name: Backend name
|
|
401
|
+
- version: Truthound version (if applicable)
|
|
402
|
+
- available: Whether backend is available
|
|
403
|
+
- capabilities: Feature capabilities dict
|
|
404
|
+
- registered_backends: List of registered backend names
|
|
405
|
+
"""
|
|
406
|
+
backend_name = BackendFactory._default_backend
|
|
407
|
+
try:
|
|
408
|
+
backend = get_backend(fallback=False)
|
|
409
|
+
available = backend.is_available()
|
|
410
|
+
version = backend.get_version()
|
|
411
|
+
except Exception:
|
|
412
|
+
available = False
|
|
413
|
+
version = None
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
"name": backend_name,
|
|
417
|
+
"version": version or get_truthound_version(),
|
|
418
|
+
"available": available,
|
|
419
|
+
"capabilities": get_backend_capabilities(),
|
|
420
|
+
"registered_backends": list(BackendFactory._backends.keys()) if BackendFactory._initialized else ["truthound", "mock"],
|
|
421
|
+
"fallback_enabled": bool(BackendFactory._fallback_backend),
|
|
422
|
+
"fallback_backend": BackendFactory._fallback_backend or None,
|
|
423
|
+
}
|