truthound-dashboard 1.0.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 (62) hide show
  1. truthound_dashboard/__init__.py +11 -0
  2. truthound_dashboard/__main__.py +6 -0
  3. truthound_dashboard/api/__init__.py +15 -0
  4. truthound_dashboard/api/deps.py +153 -0
  5. truthound_dashboard/api/drift.py +179 -0
  6. truthound_dashboard/api/error_handlers.py +287 -0
  7. truthound_dashboard/api/health.py +78 -0
  8. truthound_dashboard/api/history.py +62 -0
  9. truthound_dashboard/api/middleware.py +626 -0
  10. truthound_dashboard/api/notifications.py +561 -0
  11. truthound_dashboard/api/profile.py +52 -0
  12. truthound_dashboard/api/router.py +83 -0
  13. truthound_dashboard/api/rules.py +277 -0
  14. truthound_dashboard/api/schedules.py +329 -0
  15. truthound_dashboard/api/schemas.py +136 -0
  16. truthound_dashboard/api/sources.py +229 -0
  17. truthound_dashboard/api/validations.py +125 -0
  18. truthound_dashboard/cli.py +226 -0
  19. truthound_dashboard/config.py +132 -0
  20. truthound_dashboard/core/__init__.py +264 -0
  21. truthound_dashboard/core/base.py +185 -0
  22. truthound_dashboard/core/cache.py +479 -0
  23. truthound_dashboard/core/connections.py +331 -0
  24. truthound_dashboard/core/encryption.py +409 -0
  25. truthound_dashboard/core/exceptions.py +627 -0
  26. truthound_dashboard/core/logging.py +488 -0
  27. truthound_dashboard/core/maintenance.py +542 -0
  28. truthound_dashboard/core/notifications/__init__.py +56 -0
  29. truthound_dashboard/core/notifications/base.py +390 -0
  30. truthound_dashboard/core/notifications/channels.py +557 -0
  31. truthound_dashboard/core/notifications/dispatcher.py +453 -0
  32. truthound_dashboard/core/notifications/events.py +155 -0
  33. truthound_dashboard/core/notifications/service.py +744 -0
  34. truthound_dashboard/core/sampling.py +626 -0
  35. truthound_dashboard/core/scheduler.py +311 -0
  36. truthound_dashboard/core/services.py +1531 -0
  37. truthound_dashboard/core/truthound_adapter.py +659 -0
  38. truthound_dashboard/db/__init__.py +67 -0
  39. truthound_dashboard/db/base.py +108 -0
  40. truthound_dashboard/db/database.py +196 -0
  41. truthound_dashboard/db/models.py +732 -0
  42. truthound_dashboard/db/repository.py +237 -0
  43. truthound_dashboard/main.py +309 -0
  44. truthound_dashboard/schemas/__init__.py +150 -0
  45. truthound_dashboard/schemas/base.py +96 -0
  46. truthound_dashboard/schemas/drift.py +118 -0
  47. truthound_dashboard/schemas/history.py +74 -0
  48. truthound_dashboard/schemas/profile.py +91 -0
  49. truthound_dashboard/schemas/rule.py +199 -0
  50. truthound_dashboard/schemas/schedule.py +88 -0
  51. truthound_dashboard/schemas/schema.py +121 -0
  52. truthound_dashboard/schemas/source.py +138 -0
  53. truthound_dashboard/schemas/validation.py +192 -0
  54. truthound_dashboard/static/assets/index-BqJMyAHX.js +110 -0
  55. truthound_dashboard/static/assets/index-DMDxHCTs.js +465 -0
  56. truthound_dashboard/static/assets/index-Dm2D11TK.css +1 -0
  57. truthound_dashboard/static/index.html +15 -0
  58. truthound_dashboard/static/mockServiceWorker.js +349 -0
  59. truthound_dashboard-1.0.0.dist-info/METADATA +218 -0
  60. truthound_dashboard-1.0.0.dist-info/RECORD +62 -0
  61. truthound_dashboard-1.0.0.dist-info/WHEEL +4 -0
  62. truthound_dashboard-1.0.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,121 @@
1
+ """Schema-related Pydantic schemas.
2
+
3
+ This module defines schemas for learned schema API operations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any
10
+
11
+ from pydantic import Field
12
+
13
+ from .base import BaseSchema, IDMixin, TimestampMixin
14
+
15
+
16
+ class ColumnSchema(BaseSchema):
17
+ """Schema for a single column."""
18
+
19
+ name: str = Field(..., description="Column name")
20
+ dtype: str = Field(..., description="Data type")
21
+ nullable: bool = Field(default=True, description="Whether nulls are allowed")
22
+ unique: bool = Field(default=False, description="Whether values must be unique")
23
+ min_value: Any | None = Field(default=None, description="Minimum value constraint")
24
+ max_value: Any | None = Field(default=None, description="Maximum value constraint")
25
+ allowed_values: list[Any] | None = Field(
26
+ default=None,
27
+ description="List of allowed values",
28
+ )
29
+ pattern: str | None = Field(default=None, description="Regex pattern constraint")
30
+ min_length: int | None = Field(default=None, description="Minimum string length")
31
+ max_length: int | None = Field(default=None, description="Maximum string length")
32
+
33
+ # Statistics
34
+ null_ratio: float | None = Field(default=None, description="Ratio of null values")
35
+ unique_ratio: float | None = Field(
36
+ default=None, description="Ratio of unique values"
37
+ )
38
+ mean: float | None = Field(default=None, description="Mean value (numeric)")
39
+ std: float | None = Field(default=None, description="Standard deviation (numeric)")
40
+
41
+
42
+ class SchemaLearnRequest(BaseSchema):
43
+ """Request to learn schema from source."""
44
+
45
+ infer_constraints: bool = Field(
46
+ default=True,
47
+ description="Infer constraints from data statistics",
48
+ )
49
+
50
+
51
+ class SchemaUpdateRequest(BaseSchema):
52
+ """Request to update schema YAML."""
53
+
54
+ schema_yaml: str = Field(
55
+ ...,
56
+ min_length=1,
57
+ description="Updated schema in YAML format",
58
+ )
59
+
60
+
61
+ class SchemaResponse(BaseSchema, IDMixin, TimestampMixin):
62
+ """Full schema response."""
63
+
64
+ source_id: str = Field(..., description="Parent source ID")
65
+ schema_yaml: str = Field(..., description="Schema in YAML format")
66
+ schema_json: dict[str, Any] | None = Field(
67
+ default=None,
68
+ description="Schema as JSON object",
69
+ )
70
+ row_count: int | None = Field(
71
+ default=None,
72
+ description="Row count when schema was learned",
73
+ )
74
+ column_count: int | None = Field(
75
+ default=None,
76
+ description="Number of columns in schema",
77
+ )
78
+ columns: list[str] = Field(
79
+ default_factory=list,
80
+ description="List of column names",
81
+ )
82
+ version: str | None = Field(default=None, description="Schema version")
83
+ is_active: bool = Field(default=True, description="Whether this schema is active")
84
+
85
+ @classmethod
86
+ def from_model(cls, schema: Any) -> SchemaResponse:
87
+ """Create response from model.
88
+
89
+ Args:
90
+ schema: Schema model instance.
91
+
92
+ Returns:
93
+ SchemaResponse instance.
94
+ """
95
+ columns = []
96
+ if schema.schema_json and "columns" in schema.schema_json:
97
+ columns = list(schema.schema_json["columns"].keys())
98
+
99
+ return cls(
100
+ id=schema.id,
101
+ source_id=schema.source_id,
102
+ schema_yaml=schema.schema_yaml,
103
+ schema_json=schema.schema_json,
104
+ row_count=schema.row_count,
105
+ column_count=schema.column_count,
106
+ columns=columns,
107
+ version=schema.version,
108
+ is_active=schema.is_active,
109
+ created_at=schema.created_at,
110
+ updated_at=schema.updated_at,
111
+ )
112
+
113
+
114
+ class SchemaSummary(IDMixin):
115
+ """Minimal schema summary."""
116
+
117
+ source_id: str
118
+ column_count: int | None = None
119
+ row_count: int | None = None
120
+ is_active: bool = True
121
+ created_at: datetime
@@ -0,0 +1,138 @@
1
+ """Source-related Pydantic schemas.
2
+
3
+ This module defines schemas for data source API operations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any, Literal
10
+
11
+ from pydantic import Field, field_validator
12
+
13
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
14
+
15
+ # Supported source types
16
+ SourceType = Literal["file", "postgresql", "mysql", "snowflake", "bigquery"]
17
+
18
+
19
+ class SourceBase(BaseSchema):
20
+ """Base source schema with common fields."""
21
+
22
+ name: str = Field(
23
+ ...,
24
+ min_length=1,
25
+ max_length=255,
26
+ description="Human-readable source name",
27
+ examples=["Sales Data", "User Events"],
28
+ )
29
+ type: SourceType = Field(
30
+ ...,
31
+ description="Data source type",
32
+ examples=["file", "postgresql"],
33
+ )
34
+ config: dict[str, Any] = Field(
35
+ ...,
36
+ description="Source-specific configuration",
37
+ examples=[{"path": "/data/sales.csv"}],
38
+ )
39
+ description: str | None = Field(
40
+ default=None,
41
+ max_length=1000,
42
+ description="Optional source description",
43
+ )
44
+
45
+
46
+ class SourceCreate(SourceBase):
47
+ """Schema for creating a new source."""
48
+
49
+ @field_validator("config")
50
+ @classmethod
51
+ def validate_config(cls, v: dict[str, Any]) -> dict[str, Any]:
52
+ """Validate configuration has required fields."""
53
+ if not v:
54
+ raise ValueError("Config cannot be empty")
55
+ return v
56
+
57
+
58
+ class SourceUpdate(BaseSchema):
59
+ """Schema for updating an existing source."""
60
+
61
+ name: str | None = Field(
62
+ default=None,
63
+ min_length=1,
64
+ max_length=255,
65
+ description="New source name",
66
+ )
67
+ config: dict[str, Any] | None = Field(
68
+ default=None,
69
+ description="New source configuration",
70
+ )
71
+ description: str | None = Field(
72
+ default=None,
73
+ max_length=1000,
74
+ description="New description",
75
+ )
76
+ is_active: bool | None = Field(
77
+ default=None,
78
+ description="Whether source is active",
79
+ )
80
+
81
+
82
+ class SourceResponse(SourceBase, IDMixin, TimestampMixin):
83
+ """Schema for source responses."""
84
+
85
+ is_active: bool = Field(default=True, description="Whether source is active")
86
+ last_validated_at: datetime | None = Field(
87
+ default=None,
88
+ description="Last validation timestamp",
89
+ )
90
+
91
+ # Computed properties from relationships
92
+ has_schema: bool = Field(default=False, description="Whether schema exists")
93
+ latest_validation_status: str | None = Field(
94
+ default=None,
95
+ description="Status of latest validation",
96
+ )
97
+
98
+ @classmethod
99
+ def from_model(cls, source: Any) -> SourceResponse:
100
+ """Create response from model with computed fields.
101
+
102
+ Args:
103
+ source: Source model instance.
104
+
105
+ Returns:
106
+ SourceResponse with computed fields.
107
+ """
108
+ return cls(
109
+ id=source.id,
110
+ name=source.name,
111
+ type=source.type,
112
+ config=source.config,
113
+ description=source.description,
114
+ is_active=source.is_active,
115
+ created_at=source.created_at,
116
+ updated_at=source.updated_at,
117
+ last_validated_at=source.last_validated_at,
118
+ has_schema=source.latest_schema is not None,
119
+ latest_validation_status=(
120
+ source.latest_validation.status if source.latest_validation else None
121
+ ),
122
+ )
123
+
124
+
125
+ class SourceListResponse(ListResponseWrapper[SourceResponse]):
126
+ """Paginated source list response."""
127
+
128
+ pass
129
+
130
+
131
+ class SourceSummary(BaseSchema):
132
+ """Minimal source summary for lists."""
133
+
134
+ id: str
135
+ name: str
136
+ type: SourceType
137
+ is_active: bool
138
+ last_validated_at: datetime | None = None
@@ -0,0 +1,192 @@
1
+ """Validation-related Pydantic schemas.
2
+
3
+ This module defines schemas for validation API operations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any, Literal
10
+
11
+ from pydantic import Field
12
+
13
+ from .base import BaseSchema, IDMixin, ListResponseWrapper
14
+
15
+ # Validation status types
16
+ ValidationStatus = Literal["pending", "running", "success", "failed", "error"]
17
+
18
+ # Issue severity types
19
+ IssueSeverity = Literal["critical", "high", "medium", "low"]
20
+
21
+
22
+ class ValidationIssue(BaseSchema):
23
+ """Single validation issue.
24
+
25
+ Represents one issue found during validation, mapping to
26
+ truthound's ValidationIssue dataclass.
27
+ """
28
+
29
+ column: str = Field(..., description="Column where issue was found")
30
+ issue_type: str = Field(
31
+ ..., description="Type of issue", examples=["null_values", "type_mismatch"]
32
+ )
33
+ count: int = Field(..., ge=0, description="Number of occurrences")
34
+ severity: IssueSeverity = Field(..., description="Issue severity level")
35
+ details: str | None = Field(default=None, description="Detailed description")
36
+ expected: Any | None = Field(default=None, description="Expected value/type")
37
+ actual: Any | None = Field(default=None, description="Actual value/type found")
38
+ sample_values: list[Any] | None = Field(
39
+ default=None,
40
+ description="Sample problematic values",
41
+ )
42
+
43
+
44
+ class ValidationRunRequest(BaseSchema):
45
+ """Request to run validation on a source."""
46
+
47
+ validators: list[str] | None = Field(
48
+ default=None,
49
+ description="Specific validators to run. If None, all validators are used.",
50
+ examples=[["null", "duplicate", "schema"]],
51
+ )
52
+ schema_path: str | None = Field(
53
+ default=None,
54
+ description="Path to schema YAML file for schema validation",
55
+ )
56
+ auto_schema: bool = Field(
57
+ default=False,
58
+ description="Auto-learn and cache schema for validation",
59
+ )
60
+
61
+
62
+ class ValidationSummary(BaseSchema):
63
+ """Summary statistics for a validation run."""
64
+
65
+ passed: bool = Field(..., description="Whether validation passed")
66
+ has_critical: bool = Field(default=False, description="Has critical issues")
67
+ has_high: bool = Field(default=False, description="Has high severity issues")
68
+ total_issues: int = Field(default=0, ge=0, description="Total issue count")
69
+ critical_issues: int = Field(default=0, ge=0, description="Critical issue count")
70
+ high_issues: int = Field(default=0, ge=0, description="High severity issue count")
71
+ medium_issues: int = Field(
72
+ default=0, ge=0, description="Medium severity issue count"
73
+ )
74
+ low_issues: int = Field(default=0, ge=0, description="Low severity issue count")
75
+
76
+
77
+ class ValidationResponse(IDMixin, ValidationSummary):
78
+ """Full validation response with all details."""
79
+
80
+ source_id: str = Field(..., description="Source that was validated")
81
+ status: ValidationStatus = Field(..., description="Current validation status")
82
+
83
+ # Data statistics
84
+ row_count: int | None = Field(default=None, description="Number of rows validated")
85
+ column_count: int | None = Field(default=None, description="Number of columns")
86
+
87
+ # Issues list (full details)
88
+ issues: list[ValidationIssue] = Field(
89
+ default_factory=list,
90
+ description="List of validation issues",
91
+ )
92
+
93
+ # Error info (if status is 'error')
94
+ error_message: str | None = Field(
95
+ default=None,
96
+ description="Error message if validation failed",
97
+ )
98
+
99
+ # Timing
100
+ duration_ms: int | None = Field(
101
+ default=None,
102
+ ge=0,
103
+ description="Validation duration in milliseconds",
104
+ )
105
+ started_at: datetime | None = Field(default=None, description="Start timestamp")
106
+ completed_at: datetime | None = Field(
107
+ default=None, description="Completion timestamp"
108
+ )
109
+ created_at: datetime = Field(..., description="Record creation timestamp")
110
+
111
+ @classmethod
112
+ def from_model(cls, validation: Any) -> ValidationResponse:
113
+ """Create response from model.
114
+
115
+ Args:
116
+ validation: Validation model instance.
117
+
118
+ Returns:
119
+ ValidationResponse instance.
120
+ """
121
+ issues = []
122
+ if validation.result_json and "issues" in validation.result_json:
123
+ issues = [
124
+ ValidationIssue(**issue) for issue in validation.result_json["issues"]
125
+ ]
126
+
127
+ return cls(
128
+ id=validation.id,
129
+ source_id=validation.source_id,
130
+ status=validation.status,
131
+ passed=validation.passed or False,
132
+ has_critical=validation.has_critical or False,
133
+ has_high=validation.has_high or False,
134
+ total_issues=validation.total_issues or 0,
135
+ critical_issues=validation.critical_issues or 0,
136
+ high_issues=validation.high_issues or 0,
137
+ medium_issues=validation.medium_issues or 0,
138
+ low_issues=validation.low_issues or 0,
139
+ row_count=validation.row_count,
140
+ column_count=validation.column_count,
141
+ issues=issues,
142
+ error_message=validation.error_message,
143
+ duration_ms=validation.duration_ms,
144
+ started_at=validation.started_at,
145
+ completed_at=validation.completed_at,
146
+ created_at=validation.created_at,
147
+ )
148
+
149
+
150
+ class ValidationListItem(IDMixin, ValidationSummary):
151
+ """Validation list item (without full issues)."""
152
+
153
+ source_id: str
154
+ status: ValidationStatus
155
+ row_count: int | None = None
156
+ column_count: int | None = None
157
+ duration_ms: int | None = None
158
+ created_at: datetime
159
+
160
+ @classmethod
161
+ def from_model(cls, validation: Any) -> ValidationListItem:
162
+ """Create list item from model.
163
+
164
+ Args:
165
+ validation: Validation model instance.
166
+
167
+ Returns:
168
+ ValidationListItem instance.
169
+ """
170
+ return cls(
171
+ id=validation.id,
172
+ source_id=validation.source_id,
173
+ status=validation.status,
174
+ passed=validation.passed or False,
175
+ has_critical=validation.has_critical or False,
176
+ has_high=validation.has_high or False,
177
+ total_issues=validation.total_issues or 0,
178
+ critical_issues=validation.critical_issues or 0,
179
+ high_issues=validation.high_issues or 0,
180
+ medium_issues=validation.medium_issues or 0,
181
+ low_issues=validation.low_issues or 0,
182
+ row_count=validation.row_count,
183
+ column_count=validation.column_count,
184
+ duration_ms=validation.duration_ms,
185
+ created_at=validation.created_at,
186
+ )
187
+
188
+
189
+ class ValidationListResponse(ListResponseWrapper[ValidationListItem]):
190
+ """Paginated validation list response."""
191
+
192
+ pass