truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.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.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -379,6 +379,7 @@ class ValidationService:
379
379
  """Service for running and managing validations.
380
380
 
381
381
  Handles validation execution, result storage, and history.
382
+ Supports both built-in truthound validators and custom validators.
382
383
  """
383
384
 
384
385
  def __init__(self, session: AsyncSession) -> None:
@@ -399,6 +400,7 @@ class ValidationService:
399
400
  *,
400
401
  validators: list[str] | None = None,
401
402
  validator_params: dict[str, dict[str, Any]] | None = None,
403
+ custom_validators: list[dict[str, Any]] | None = None,
402
404
  schema_path: str | None = None,
403
405
  auto_schema: bool = False,
404
406
  columns: list[str] | None = None,
@@ -411,7 +413,8 @@ class ValidationService:
411
413
  """Run validation on a source.
412
414
 
413
415
  This method provides full access to truthound's th.check() parameters,
414
- allowing fine-grained control over validation behavior.
416
+ allowing fine-grained control over validation behavior. It also supports
417
+ running custom validators alongside built-in validators.
415
418
 
416
419
  Args:
417
420
  source_id: Source ID to validate.
@@ -420,6 +423,8 @@ class ValidationService:
420
423
  Format: {"ValidatorName": {"param1": value1, "param2": value2}}
421
424
  Example: {"Null": {"columns": ["email"], "mostly": 0.95},
422
425
  "CompletenessRatio": {"column": "phone", "min_ratio": 0.98}}
426
+ custom_validators: Optional list of custom validator configs.
427
+ Format: [{"validator_id": "...", "column": "...", "params": {...}}]
423
428
  schema_path: Optional schema file path.
424
429
  auto_schema: Auto-learn schema if True.
425
430
  columns: Columns to validate. If None, validates all columns.
@@ -448,7 +453,7 @@ class ValidationService:
448
453
  )
449
454
 
450
455
  try:
451
- # Run validation with all supported parameters
456
+ # Run built-in validation with all supported parameters
452
457
  result = await self.adapter.check(
453
458
  source.source_path or "",
454
459
  validators=validators,
@@ -463,8 +468,19 @@ class ValidationService:
463
468
  pushdown=pushdown,
464
469
  )
465
470
 
466
- # Update validation with results
467
- await self._update_validation_success(validation, result)
471
+ # Run custom validators if specified
472
+ custom_results = []
473
+ if custom_validators:
474
+ custom_results = await self._run_custom_validators(
475
+ source=source,
476
+ custom_validators=custom_validators,
477
+ validation_id=str(validation.id),
478
+ )
479
+
480
+ # Update validation with combined results
481
+ await self._update_validation_success(
482
+ validation, result, custom_results=custom_results
483
+ )
468
484
 
469
485
  # Update source last validated
470
486
  source.last_validated_at = datetime.utcnow()
@@ -477,29 +493,156 @@ class ValidationService:
477
493
  await self.session.refresh(validation)
478
494
  return validation
479
495
 
496
+ async def _run_custom_validators(
497
+ self,
498
+ source: Source,
499
+ custom_validators: list[dict[str, Any]],
500
+ validation_id: str,
501
+ ) -> list[dict[str, Any]]:
502
+ """Run custom validators on source data.
503
+
504
+ Args:
505
+ source: Data source to validate.
506
+ custom_validators: List of custom validator configs.
507
+ validation_id: Parent validation ID.
508
+
509
+ Returns:
510
+ List of custom validator results.
511
+ """
512
+ from truthound_dashboard.core.plugins import CustomValidatorExecutor
513
+ from truthound_dashboard.core.plugins.registry import plugin_registry
514
+ from truthound_dashboard.core.plugins.validator_executor import ValidatorContext
515
+
516
+ results = []
517
+ executor = CustomValidatorExecutor()
518
+
519
+ # Load source data once
520
+ try:
521
+ import polars as pl
522
+
523
+ source_path = source.source_path or ""
524
+ if source.type == "csv":
525
+ df = pl.read_csv(source_path)
526
+ elif source.type == "parquet":
527
+ df = pl.read_parquet(source_path)
528
+ elif source.type == "json":
529
+ df = pl.read_json(source_path)
530
+ else:
531
+ # Unsupported source type for custom validators
532
+ return results
533
+ except Exception:
534
+ return results
535
+
536
+ for cv_config in custom_validators:
537
+ validator_id = cv_config.get("validator_id")
538
+ column_name = cv_config.get("column")
539
+ params = cv_config.get("params", {})
540
+
541
+ if not validator_id or not column_name:
542
+ continue
543
+
544
+ # Get the custom validator
545
+ validator = await plugin_registry.get_validator(
546
+ self.session, validator_id=validator_id
547
+ )
548
+ if not validator or not validator.is_enabled:
549
+ continue
550
+
551
+ # Check column exists
552
+ if column_name not in df.columns:
553
+ results.append({
554
+ "validator_id": validator_id,
555
+ "validator_name": validator.name,
556
+ "column": column_name,
557
+ "passed": False,
558
+ "error": f"Column '{column_name}' not found",
559
+ })
560
+ continue
561
+
562
+ # Create context
563
+ column_values = df[column_name].to_list()
564
+ context = ValidatorContext(
565
+ column_name=column_name,
566
+ column_values=column_values,
567
+ parameters=params,
568
+ schema={"dtype": str(df[column_name].dtype)},
569
+ row_count=len(column_values),
570
+ )
571
+
572
+ # Execute
573
+ try:
574
+ result = await executor.execute(
575
+ validator=validator,
576
+ context=context,
577
+ session=self.session,
578
+ source_id=str(source.id),
579
+ )
580
+ results.append({
581
+ "validator_id": validator_id,
582
+ "validator_name": validator.name,
583
+ "column": column_name,
584
+ "passed": result.passed,
585
+ "issues": result.issues,
586
+ "message": result.message,
587
+ "execution_time_ms": result.execution_time_ms,
588
+ })
589
+ except Exception as e:
590
+ results.append({
591
+ "validator_id": validator_id,
592
+ "validator_name": validator.name,
593
+ "column": column_name,
594
+ "passed": False,
595
+ "error": str(e),
596
+ })
597
+
598
+ return results
599
+
480
600
  async def _update_validation_success(
481
601
  self,
482
602
  validation: Validation,
483
603
  result: CheckResult,
604
+ custom_results: list[dict[str, Any]] | None = None,
484
605
  ) -> None:
485
606
  """Update validation with successful result.
486
607
 
487
608
  Args:
488
609
  validation: Validation record to update.
489
- result: Check result from adapter.
610
+ result: Check result from built-in validators.
611
+ custom_results: Optional results from custom validators.
490
612
  """
491
- validation.status = "success" if result.passed else "failed"
492
- validation.passed = result.passed
613
+ # Calculate combined pass/fail status
614
+ builtin_passed = result.passed
615
+ custom_passed = True
616
+ custom_issues_count = 0
617
+
618
+ if custom_results:
619
+ for cr in custom_results:
620
+ if not cr.get("passed", True):
621
+ custom_passed = False
622
+ custom_issues_count += len(cr.get("issues", []))
623
+
624
+ combined_passed = builtin_passed and custom_passed
625
+
626
+ validation.status = "success" if combined_passed else "failed"
627
+ validation.passed = combined_passed
493
628
  validation.has_critical = result.has_critical
494
629
  validation.has_high = result.has_high
495
- validation.total_issues = result.total_issues
630
+ validation.total_issues = result.total_issues + custom_issues_count
496
631
  validation.critical_issues = result.critical_issues
497
632
  validation.high_issues = result.high_issues
498
633
  validation.medium_issues = result.medium_issues
499
634
  validation.low_issues = result.low_issues
500
635
  validation.row_count = result.row_count
501
636
  validation.column_count = result.column_count
502
- validation.result_json = result.to_dict()
637
+
638
+ # Combine results
639
+ combined_result = result.to_dict()
640
+ if custom_results:
641
+ combined_result["custom_validators"] = custom_results
642
+ combined_result["custom_validators_passed"] = custom_passed
643
+ combined_result["custom_issues_count"] = custom_issues_count
644
+
645
+ validation.result_json = combined_result
503
646
  validation.completed_at = datetime.utcnow()
504
647
 
505
648
  if validation.started_at: