truthound-dashboard 1.4.4__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.
Files changed (205) hide show
  1. truthound_dashboard/api/alerts.py +75 -86
  2. truthound_dashboard/api/anomaly.py +7 -13
  3. truthound_dashboard/api/cross_alerts.py +38 -52
  4. truthound_dashboard/api/drift.py +49 -59
  5. truthound_dashboard/api/drift_monitor.py +234 -79
  6. truthound_dashboard/api/enterprise_sampling.py +498 -0
  7. truthound_dashboard/api/history.py +57 -5
  8. truthound_dashboard/api/lineage.py +3 -48
  9. truthound_dashboard/api/maintenance.py +104 -49
  10. truthound_dashboard/api/mask.py +1 -2
  11. truthound_dashboard/api/middleware.py +2 -1
  12. truthound_dashboard/api/model_monitoring.py +435 -311
  13. truthound_dashboard/api/notifications.py +227 -191
  14. truthound_dashboard/api/notifications_advanced.py +21 -20
  15. truthound_dashboard/api/observability.py +586 -0
  16. truthound_dashboard/api/plugins.py +2 -433
  17. truthound_dashboard/api/profile.py +199 -37
  18. truthound_dashboard/api/quality_reporter.py +701 -0
  19. truthound_dashboard/api/reports.py +7 -16
  20. truthound_dashboard/api/router.py +66 -0
  21. truthound_dashboard/api/rule_suggestions.py +5 -5
  22. truthound_dashboard/api/scan.py +17 -19
  23. truthound_dashboard/api/schedules.py +85 -50
  24. truthound_dashboard/api/schema_evolution.py +6 -6
  25. truthound_dashboard/api/schema_watcher.py +667 -0
  26. truthound_dashboard/api/sources.py +98 -27
  27. truthound_dashboard/api/tiering.py +1323 -0
  28. truthound_dashboard/api/triggers.py +14 -11
  29. truthound_dashboard/api/validations.py +12 -11
  30. truthound_dashboard/api/versioning.py +1 -6
  31. truthound_dashboard/core/__init__.py +129 -3
  32. truthound_dashboard/core/actions/__init__.py +62 -0
  33. truthound_dashboard/core/actions/custom.py +426 -0
  34. truthound_dashboard/core/actions/notifications.py +910 -0
  35. truthound_dashboard/core/actions/storage.py +472 -0
  36. truthound_dashboard/core/actions/webhook.py +281 -0
  37. truthound_dashboard/core/anomaly.py +262 -67
  38. truthound_dashboard/core/anomaly_explainer.py +4 -3
  39. truthound_dashboard/core/backends/__init__.py +67 -0
  40. truthound_dashboard/core/backends/base.py +299 -0
  41. truthound_dashboard/core/backends/errors.py +191 -0
  42. truthound_dashboard/core/backends/factory.py +423 -0
  43. truthound_dashboard/core/backends/mock_backend.py +451 -0
  44. truthound_dashboard/core/backends/truthound_backend.py +718 -0
  45. truthound_dashboard/core/checkpoint/__init__.py +87 -0
  46. truthound_dashboard/core/checkpoint/adapters.py +814 -0
  47. truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
  48. truthound_dashboard/core/checkpoint/runner.py +270 -0
  49. truthound_dashboard/core/connections.py +437 -10
  50. truthound_dashboard/core/converters/__init__.py +14 -0
  51. truthound_dashboard/core/converters/truthound.py +620 -0
  52. truthound_dashboard/core/cross_alerts.py +540 -320
  53. truthound_dashboard/core/datasource_factory.py +1672 -0
  54. truthound_dashboard/core/drift_monitor.py +216 -20
  55. truthound_dashboard/core/enterprise_sampling.py +1291 -0
  56. truthound_dashboard/core/interfaces/__init__.py +225 -0
  57. truthound_dashboard/core/interfaces/actions.py +652 -0
  58. truthound_dashboard/core/interfaces/base.py +247 -0
  59. truthound_dashboard/core/interfaces/checkpoint.py +676 -0
  60. truthound_dashboard/core/interfaces/protocols.py +664 -0
  61. truthound_dashboard/core/interfaces/reporters.py +650 -0
  62. truthound_dashboard/core/interfaces/routing.py +646 -0
  63. truthound_dashboard/core/interfaces/triggers.py +619 -0
  64. truthound_dashboard/core/lineage.py +407 -71
  65. truthound_dashboard/core/model_monitoring.py +431 -3
  66. truthound_dashboard/core/notifications/base.py +4 -0
  67. truthound_dashboard/core/notifications/channels.py +501 -1203
  68. truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
  69. truthound_dashboard/core/notifications/deduplication/service.py +131 -348
  70. truthound_dashboard/core/notifications/dispatcher.py +202 -11
  71. truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
  72. truthound_dashboard/core/notifications/escalation/engine.py +168 -358
  73. truthound_dashboard/core/notifications/routing/__init__.py +88 -128
  74. truthound_dashboard/core/notifications/routing/engine.py +90 -317
  75. truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
  76. truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
  77. truthound_dashboard/core/notifications/throttling/builder.py +117 -255
  78. truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
  79. truthound_dashboard/core/phase5/collaboration.py +1 -1
  80. truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
  81. truthound_dashboard/core/quality_reporter.py +1359 -0
  82. truthound_dashboard/core/report_history.py +0 -6
  83. truthound_dashboard/core/reporters/__init__.py +175 -14
  84. truthound_dashboard/core/reporters/adapters.py +943 -0
  85. truthound_dashboard/core/reporters/base.py +0 -3
  86. truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
  87. truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
  88. truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
  89. truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
  90. truthound_dashboard/core/reporters/compat.py +266 -0
  91. truthound_dashboard/core/reporters/csv_reporter.py +2 -35
  92. truthound_dashboard/core/reporters/factory.py +526 -0
  93. truthound_dashboard/core/reporters/interfaces.py +745 -0
  94. truthound_dashboard/core/reporters/registry.py +1 -10
  95. truthound_dashboard/core/scheduler.py +165 -0
  96. truthound_dashboard/core/schema_evolution.py +3 -3
  97. truthound_dashboard/core/schema_watcher.py +1528 -0
  98. truthound_dashboard/core/services.py +595 -76
  99. truthound_dashboard/core/store_manager.py +810 -0
  100. truthound_dashboard/core/streaming_anomaly.py +169 -4
  101. truthound_dashboard/core/tiering.py +1309 -0
  102. truthound_dashboard/core/triggers/evaluators.py +178 -8
  103. truthound_dashboard/core/truthound_adapter.py +2620 -197
  104. truthound_dashboard/core/unified_alerts.py +23 -20
  105. truthound_dashboard/db/__init__.py +8 -0
  106. truthound_dashboard/db/database.py +8 -2
  107. truthound_dashboard/db/models.py +944 -25
  108. truthound_dashboard/db/repository.py +2 -0
  109. truthound_dashboard/main.py +11 -0
  110. truthound_dashboard/schemas/__init__.py +177 -16
  111. truthound_dashboard/schemas/base.py +44 -23
  112. truthound_dashboard/schemas/collaboration.py +19 -6
  113. truthound_dashboard/schemas/cross_alerts.py +19 -3
  114. truthound_dashboard/schemas/drift.py +61 -55
  115. truthound_dashboard/schemas/drift_monitor.py +67 -23
  116. truthound_dashboard/schemas/enterprise_sampling.py +653 -0
  117. truthound_dashboard/schemas/lineage.py +0 -33
  118. truthound_dashboard/schemas/mask.py +10 -8
  119. truthound_dashboard/schemas/model_monitoring.py +89 -10
  120. truthound_dashboard/schemas/notifications_advanced.py +13 -0
  121. truthound_dashboard/schemas/observability.py +453 -0
  122. truthound_dashboard/schemas/plugins.py +0 -280
  123. truthound_dashboard/schemas/profile.py +154 -247
  124. truthound_dashboard/schemas/quality_reporter.py +403 -0
  125. truthound_dashboard/schemas/reports.py +2 -2
  126. truthound_dashboard/schemas/rule_suggestion.py +8 -1
  127. truthound_dashboard/schemas/scan.py +4 -24
  128. truthound_dashboard/schemas/schedule.py +11 -3
  129. truthound_dashboard/schemas/schema_watcher.py +727 -0
  130. truthound_dashboard/schemas/source.py +17 -2
  131. truthound_dashboard/schemas/tiering.py +822 -0
  132. truthound_dashboard/schemas/triggers.py +16 -0
  133. truthound_dashboard/schemas/unified_alerts.py +7 -0
  134. truthound_dashboard/schemas/validation.py +0 -13
  135. truthound_dashboard/schemas/validators/base.py +41 -21
  136. truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
  137. truthound_dashboard/schemas/validators/localization_validators.py +273 -0
  138. truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
  139. truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
  140. truthound_dashboard/schemas/validators/referential_validators.py +312 -0
  141. truthound_dashboard/schemas/validators/registry.py +93 -8
  142. truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
  143. truthound_dashboard/schemas/versioning.py +1 -6
  144. truthound_dashboard/static/index.html +2 -2
  145. truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
  146. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
  147. truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
  148. truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
  149. truthound_dashboard/core/plugins/hooks/manager.py +0 -403
  150. truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
  151. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
  152. truthound_dashboard/core/reporters/junit_reporter.py +0 -233
  153. truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
  154. truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
  155. truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
  156. truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
  157. truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
  158. truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
  159. truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
  160. truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
  161. truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
  162. truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
  163. truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
  164. truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
  165. truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
  166. truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
  167. truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
  168. truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
  169. truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
  170. truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
  171. truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
  172. truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
  173. truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
  174. truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
  175. truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
  176. truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
  177. truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
  178. truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
  179. truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
  180. truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
  181. truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
  182. truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
  183. truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
  184. truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
  185. truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
  186. truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
  187. truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
  188. truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
  189. truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
  190. truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
  191. truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
  192. truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
  193. truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
  194. truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
  195. truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
  196. truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
  197. truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
  198. truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
  199. truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
  200. truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
  201. truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
  202. truthound_dashboard-1.4.4.dist-info/METADATA +0 -507
  203. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.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
+ }