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.
- truthound_dashboard/__init__.py +11 -0
- truthound_dashboard/__main__.py +6 -0
- truthound_dashboard/api/__init__.py +15 -0
- truthound_dashboard/api/deps.py +153 -0
- truthound_dashboard/api/drift.py +179 -0
- truthound_dashboard/api/error_handlers.py +287 -0
- truthound_dashboard/api/health.py +78 -0
- truthound_dashboard/api/history.py +62 -0
- truthound_dashboard/api/middleware.py +626 -0
- truthound_dashboard/api/notifications.py +561 -0
- truthound_dashboard/api/profile.py +52 -0
- truthound_dashboard/api/router.py +83 -0
- truthound_dashboard/api/rules.py +277 -0
- truthound_dashboard/api/schedules.py +329 -0
- truthound_dashboard/api/schemas.py +136 -0
- truthound_dashboard/api/sources.py +229 -0
- truthound_dashboard/api/validations.py +125 -0
- truthound_dashboard/cli.py +226 -0
- truthound_dashboard/config.py +132 -0
- truthound_dashboard/core/__init__.py +264 -0
- truthound_dashboard/core/base.py +185 -0
- truthound_dashboard/core/cache.py +479 -0
- truthound_dashboard/core/connections.py +331 -0
- truthound_dashboard/core/encryption.py +409 -0
- truthound_dashboard/core/exceptions.py +627 -0
- truthound_dashboard/core/logging.py +488 -0
- truthound_dashboard/core/maintenance.py +542 -0
- truthound_dashboard/core/notifications/__init__.py +56 -0
- truthound_dashboard/core/notifications/base.py +390 -0
- truthound_dashboard/core/notifications/channels.py +557 -0
- truthound_dashboard/core/notifications/dispatcher.py +453 -0
- truthound_dashboard/core/notifications/events.py +155 -0
- truthound_dashboard/core/notifications/service.py +744 -0
- truthound_dashboard/core/sampling.py +626 -0
- truthound_dashboard/core/scheduler.py +311 -0
- truthound_dashboard/core/services.py +1531 -0
- truthound_dashboard/core/truthound_adapter.py +659 -0
- truthound_dashboard/db/__init__.py +67 -0
- truthound_dashboard/db/base.py +108 -0
- truthound_dashboard/db/database.py +196 -0
- truthound_dashboard/db/models.py +732 -0
- truthound_dashboard/db/repository.py +237 -0
- truthound_dashboard/main.py +309 -0
- truthound_dashboard/schemas/__init__.py +150 -0
- truthound_dashboard/schemas/base.py +96 -0
- truthound_dashboard/schemas/drift.py +118 -0
- truthound_dashboard/schemas/history.py +74 -0
- truthound_dashboard/schemas/profile.py +91 -0
- truthound_dashboard/schemas/rule.py +199 -0
- truthound_dashboard/schemas/schedule.py +88 -0
- truthound_dashboard/schemas/schema.py +121 -0
- truthound_dashboard/schemas/source.py +138 -0
- truthound_dashboard/schemas/validation.py +192 -0
- truthound_dashboard/static/assets/index-BqJMyAHX.js +110 -0
- truthound_dashboard/static/assets/index-DMDxHCTs.js +465 -0
- truthound_dashboard/static/assets/index-Dm2D11TK.css +1 -0
- truthound_dashboard/static/index.html +15 -0
- truthound_dashboard/static/mockServiceWorker.js +349 -0
- truthound_dashboard-1.0.0.dist-info/METADATA +218 -0
- truthound_dashboard-1.0.0.dist-info/RECORD +62 -0
- truthound_dashboard-1.0.0.dist-info/WHEEL +4 -0
- 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
|