truthound-dashboard 1.1.0__py3-none-any.whl → 1.2.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.
@@ -54,6 +54,66 @@ from .schedule import (
54
54
  ScheduleResponse,
55
55
  ScheduleUpdate,
56
56
  )
57
+ # Phase 5: Glossary schemas
58
+ from .glossary import (
59
+ CategoryBase,
60
+ CategoryCreate,
61
+ CategoryListResponse,
62
+ CategoryResponse,
63
+ CategorySummary,
64
+ CategoryUpdate,
65
+ RelatedTermSummary,
66
+ RelationshipBase,
67
+ RelationshipCreate,
68
+ RelationshipListResponse,
69
+ RelationshipResponse,
70
+ RelationshipType,
71
+ TermBase,
72
+ TermCreate,
73
+ TermHistoryListResponse,
74
+ TermHistoryResponse,
75
+ TermListItem,
76
+ TermListResponse,
77
+ TermResponse,
78
+ TermStatus,
79
+ TermSummary,
80
+ TermUpdate,
81
+ )
82
+ # Phase 5: Catalog schemas
83
+ from .catalog import (
84
+ AssetBase,
85
+ AssetCreate,
86
+ AssetListItem,
87
+ AssetListResponse,
88
+ AssetResponse,
89
+ AssetType,
90
+ AssetUpdate,
91
+ ColumnBase,
92
+ ColumnCreate,
93
+ ColumnListResponse,
94
+ ColumnResponse,
95
+ ColumnTermMapping,
96
+ ColumnUpdate,
97
+ QualityLevel,
98
+ SensitivityLevel,
99
+ SourceSummary,
100
+ TagBase,
101
+ TagCreate,
102
+ TagResponse,
103
+ )
104
+ # Phase 5: Collaboration schemas
105
+ from .collaboration import (
106
+ ActivityAction,
107
+ ActivityCreate,
108
+ ActivityListResponse,
109
+ ActivityResponse,
110
+ CommentBase,
111
+ CommentCreate,
112
+ CommentListResponse,
113
+ CommentResponse,
114
+ CommentUpdate,
115
+ ResourceType,
116
+ )
57
117
  from .schema import (
58
118
  ColumnSchema,
59
119
  SchemaLearnRequest,
@@ -147,4 +207,58 @@ __all__ = [
147
207
  "ScheduleListItem",
148
208
  "ScheduleListResponse",
149
209
  "ScheduleActionResponse",
210
+ # Phase 5: Glossary
211
+ "TermStatus",
212
+ "RelationshipType",
213
+ "CategoryBase",
214
+ "CategoryCreate",
215
+ "CategoryUpdate",
216
+ "CategorySummary",
217
+ "CategoryResponse",
218
+ "CategoryListResponse",
219
+ "TermBase",
220
+ "TermCreate",
221
+ "TermUpdate",
222
+ "TermSummary",
223
+ "RelatedTermSummary",
224
+ "TermResponse",
225
+ "TermListItem",
226
+ "TermListResponse",
227
+ "RelationshipBase",
228
+ "RelationshipCreate",
229
+ "RelationshipResponse",
230
+ "RelationshipListResponse",
231
+ "TermHistoryResponse",
232
+ "TermHistoryListResponse",
233
+ # Phase 5: Catalog
234
+ "AssetType",
235
+ "SensitivityLevel",
236
+ "QualityLevel",
237
+ "TagBase",
238
+ "TagCreate",
239
+ "TagResponse",
240
+ "ColumnBase",
241
+ "ColumnCreate",
242
+ "ColumnUpdate",
243
+ "ColumnTermMapping",
244
+ "ColumnResponse",
245
+ "ColumnListResponse",
246
+ "SourceSummary",
247
+ "AssetBase",
248
+ "AssetCreate",
249
+ "AssetUpdate",
250
+ "AssetResponse",
251
+ "AssetListItem",
252
+ "AssetListResponse",
253
+ # Phase 5: Collaboration
254
+ "ResourceType",
255
+ "ActivityAction",
256
+ "CommentBase",
257
+ "CommentCreate",
258
+ "CommentUpdate",
259
+ "CommentResponse",
260
+ "CommentListResponse",
261
+ "ActivityResponse",
262
+ "ActivityListResponse",
263
+ "ActivityCreate",
150
264
  ]
@@ -0,0 +1,352 @@
1
+ """Pydantic schemas for Data Catalog API.
2
+
3
+ This module defines request/response schemas for catalog assets,
4
+ columns, and tags.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from enum import Enum
11
+
12
+ from pydantic import Field, field_validator
13
+
14
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
15
+ from .glossary import TermSummary
16
+
17
+
18
+ # =============================================================================
19
+ # Enums
20
+ # =============================================================================
21
+
22
+
23
+ class AssetType(str, Enum):
24
+ """Type of catalog asset."""
25
+
26
+ TABLE = "table"
27
+ FILE = "file"
28
+ API = "api"
29
+
30
+
31
+ class SensitivityLevel(str, Enum):
32
+ """Sensitivity level for data columns."""
33
+
34
+ PUBLIC = "public"
35
+ INTERNAL = "internal"
36
+ CONFIDENTIAL = "confidential"
37
+ RESTRICTED = "restricted"
38
+
39
+
40
+ class QualityLevel(str, Enum):
41
+ """Quality level based on score."""
42
+
43
+ UNKNOWN = "unknown"
44
+ POOR = "poor"
45
+ FAIR = "fair"
46
+ GOOD = "good"
47
+ EXCELLENT = "excellent"
48
+
49
+
50
+ # =============================================================================
51
+ # Tag Schemas
52
+ # =============================================================================
53
+
54
+
55
+ class TagBase(BaseSchema):
56
+ """Base schema for asset tags."""
57
+
58
+ tag_name: str = Field(..., min_length=1, max_length=100, description="Tag name")
59
+ tag_value: str | None = Field(None, max_length=255, description="Tag value")
60
+
61
+
62
+ class TagCreate(TagBase):
63
+ """Schema for creating an asset tag."""
64
+
65
+ @field_validator("tag_name")
66
+ @classmethod
67
+ def validate_tag_name(cls, v: str) -> str:
68
+ """Validate and normalize tag name."""
69
+ return v.strip().lower()
70
+
71
+
72
+ class TagResponse(BaseSchema, IDMixin):
73
+ """Response schema for an asset tag."""
74
+
75
+ tag_name: str
76
+ tag_value: str | None
77
+ created_at: datetime
78
+
79
+ @classmethod
80
+ def from_model(cls, tag: any) -> TagResponse:
81
+ """Create response from model."""
82
+ return cls(
83
+ id=tag.id,
84
+ tag_name=tag.tag_name,
85
+ tag_value=tag.tag_value,
86
+ created_at=tag.created_at,
87
+ )
88
+
89
+
90
+ # =============================================================================
91
+ # Column Schemas
92
+ # =============================================================================
93
+
94
+
95
+ class ColumnBase(BaseSchema):
96
+ """Base schema for asset columns."""
97
+
98
+ name: str = Field(..., min_length=1, max_length=255, description="Column name")
99
+ data_type: str | None = Field(None, max_length=100, description="Data type")
100
+ description: str | None = Field(None, description="Column description")
101
+ is_nullable: bool = Field(default=True, description="Whether column allows nulls")
102
+ is_primary_key: bool = Field(default=False, description="Whether column is PK")
103
+ sensitivity_level: SensitivityLevel | None = Field(
104
+ default=SensitivityLevel.PUBLIC,
105
+ description="Data sensitivity level",
106
+ )
107
+
108
+
109
+ class ColumnCreate(ColumnBase):
110
+ """Schema for creating an asset column."""
111
+
112
+ pass
113
+
114
+
115
+ class ColumnUpdate(BaseSchema):
116
+ """Schema for updating an asset column."""
117
+
118
+ name: str | None = Field(None, min_length=1, max_length=255)
119
+ data_type: str | None = None
120
+ description: str | None = None
121
+ is_nullable: bool | None = None
122
+ is_primary_key: bool | None = None
123
+ sensitivity_level: SensitivityLevel | None = None
124
+
125
+
126
+ class ColumnTermMapping(BaseSchema):
127
+ """Schema for mapping a column to a glossary term."""
128
+
129
+ term_id: str = Field(..., description="Glossary term ID to map")
130
+
131
+
132
+ class ColumnResponse(BaseSchema, IDMixin, TimestampMixin):
133
+ """Response schema for an asset column."""
134
+
135
+ asset_id: str
136
+ name: str
137
+ data_type: str | None
138
+ description: str | None
139
+ is_nullable: bool
140
+ is_primary_key: bool
141
+ term_id: str | None
142
+ term: TermSummary | None = None
143
+ sensitivity_level: SensitivityLevel | None
144
+ is_sensitive: bool = False
145
+ has_term_mapping: bool = False
146
+
147
+ @classmethod
148
+ def from_model(cls, column: any) -> ColumnResponse:
149
+ """Create response from model."""
150
+ from .glossary import TermStatus
151
+
152
+ term_summary = None
153
+ if column.term:
154
+ term_summary = TermSummary(
155
+ id=column.term.id,
156
+ name=column.term.name,
157
+ status=TermStatus(column.term.status),
158
+ )
159
+
160
+ sensitivity = None
161
+ if column.sensitivity_level:
162
+ sensitivity = SensitivityLevel(column.sensitivity_level)
163
+
164
+ return cls(
165
+ id=column.id,
166
+ asset_id=column.asset_id,
167
+ name=column.name,
168
+ data_type=column.data_type,
169
+ description=column.description,
170
+ is_nullable=column.is_nullable,
171
+ is_primary_key=column.is_primary_key,
172
+ term_id=column.term_id,
173
+ term=term_summary,
174
+ sensitivity_level=sensitivity,
175
+ is_sensitive=column.is_sensitive,
176
+ has_term_mapping=column.has_term_mapping,
177
+ created_at=column.created_at,
178
+ updated_at=column.updated_at,
179
+ )
180
+
181
+
182
+ class ColumnListResponse(ListResponseWrapper[ColumnResponse]):
183
+ """List of columns."""
184
+
185
+ pass
186
+
187
+
188
+ # =============================================================================
189
+ # Asset Schemas
190
+ # =============================================================================
191
+
192
+
193
+ class SourceSummary(BaseSchema, IDMixin):
194
+ """Summary schema for data source references."""
195
+
196
+ name: str
197
+ type: str
198
+
199
+
200
+ class AssetBase(BaseSchema):
201
+ """Base schema for catalog assets."""
202
+
203
+ name: str = Field(..., min_length=1, max_length=255, description="Asset name")
204
+ asset_type: AssetType = Field(
205
+ default=AssetType.TABLE,
206
+ description="Asset type",
207
+ )
208
+ source_id: str | None = Field(None, description="Linked data source ID")
209
+ description: str | None = Field(None, description="Asset description")
210
+ owner_id: str | None = Field(None, description="Owner identifier")
211
+
212
+
213
+ class AssetCreate(AssetBase):
214
+ """Schema for creating a catalog asset."""
215
+
216
+ columns: list[ColumnCreate] = Field(
217
+ default_factory=list,
218
+ description="Initial columns to create",
219
+ )
220
+ tags: list[TagCreate] = Field(
221
+ default_factory=list,
222
+ description="Initial tags to add",
223
+ )
224
+
225
+ @field_validator("name")
226
+ @classmethod
227
+ def validate_name(cls, v: str) -> str:
228
+ """Validate asset name."""
229
+ return v.strip()
230
+
231
+
232
+ class AssetUpdate(BaseSchema):
233
+ """Schema for updating a catalog asset."""
234
+
235
+ name: str | None = Field(None, min_length=1, max_length=255)
236
+ asset_type: AssetType | None = None
237
+ source_id: str | None = None
238
+ description: str | None = None
239
+ owner_id: str | None = None
240
+ quality_score: float | None = Field(None, ge=0, le=100)
241
+
242
+
243
+ class AssetResponse(BaseSchema, IDMixin, TimestampMixin):
244
+ """Response schema for a catalog asset."""
245
+
246
+ name: str
247
+ asset_type: AssetType
248
+ source_id: str | None
249
+ source: SourceSummary | None = None
250
+ description: str | None
251
+ owner_id: str | None
252
+ quality_score: float | None
253
+ quality_level: QualityLevel = QualityLevel.UNKNOWN
254
+ column_count: int = 0
255
+ columns: list[ColumnResponse] = Field(default_factory=list)
256
+ tags: list[TagResponse] = Field(default_factory=list)
257
+
258
+ @classmethod
259
+ def from_model(cls, asset: any, include_columns: bool = True) -> AssetResponse:
260
+ """Create response from model."""
261
+ source_summary = None
262
+ if asset.source:
263
+ source_summary = SourceSummary(
264
+ id=asset.source.id,
265
+ name=asset.source.name,
266
+ type=asset.source.type,
267
+ )
268
+
269
+ columns = []
270
+ if include_columns:
271
+ columns = [ColumnResponse.from_model(c) for c in asset.columns]
272
+
273
+ tags = [TagResponse.from_model(t) for t in asset.tags]
274
+
275
+ quality_level = QualityLevel.UNKNOWN
276
+ if asset.quality_score is not None:
277
+ if asset.quality_score >= 90:
278
+ quality_level = QualityLevel.EXCELLENT
279
+ elif asset.quality_score >= 70:
280
+ quality_level = QualityLevel.GOOD
281
+ elif asset.quality_score >= 50:
282
+ quality_level = QualityLevel.FAIR
283
+ else:
284
+ quality_level = QualityLevel.POOR
285
+
286
+ return cls(
287
+ id=asset.id,
288
+ name=asset.name,
289
+ asset_type=AssetType(asset.asset_type),
290
+ source_id=asset.source_id,
291
+ source=source_summary,
292
+ description=asset.description,
293
+ owner_id=asset.owner_id,
294
+ quality_score=asset.quality_score,
295
+ quality_level=quality_level,
296
+ column_count=asset.column_count,
297
+ columns=columns,
298
+ tags=tags,
299
+ created_at=asset.created_at,
300
+ updated_at=asset.updated_at,
301
+ )
302
+
303
+
304
+ class AssetListItem(BaseSchema, IDMixin, TimestampMixin):
305
+ """List item schema for assets (lighter than full response)."""
306
+
307
+ name: str
308
+ asset_type: AssetType
309
+ source_id: str | None
310
+ source_name: str | None = None
311
+ description: str | None
312
+ owner_id: str | None
313
+ quality_score: float | None
314
+ quality_level: QualityLevel = QualityLevel.UNKNOWN
315
+ column_count: int = 0
316
+ tag_names: list[str] = Field(default_factory=list)
317
+
318
+ @classmethod
319
+ def from_model(cls, asset: any) -> AssetListItem:
320
+ """Create list item from model."""
321
+ quality_level = QualityLevel.UNKNOWN
322
+ if asset.quality_score is not None:
323
+ if asset.quality_score >= 90:
324
+ quality_level = QualityLevel.EXCELLENT
325
+ elif asset.quality_score >= 70:
326
+ quality_level = QualityLevel.GOOD
327
+ elif asset.quality_score >= 50:
328
+ quality_level = QualityLevel.FAIR
329
+ else:
330
+ quality_level = QualityLevel.POOR
331
+
332
+ return cls(
333
+ id=asset.id,
334
+ name=asset.name,
335
+ asset_type=AssetType(asset.asset_type),
336
+ source_id=asset.source_id,
337
+ source_name=asset.source.name if asset.source else None,
338
+ description=asset.description,
339
+ owner_id=asset.owner_id,
340
+ quality_score=asset.quality_score,
341
+ quality_level=quality_level,
342
+ column_count=asset.column_count,
343
+ tag_names=asset.tag_names,
344
+ created_at=asset.created_at,
345
+ updated_at=asset.updated_at,
346
+ )
347
+
348
+
349
+ class AssetListResponse(ListResponseWrapper[AssetListItem]):
350
+ """Paginated list of assets."""
351
+
352
+ pass
@@ -0,0 +1,169 @@
1
+ """Pydantic schemas for Collaboration API.
2
+
3
+ This module defines request/response schemas for comments
4
+ and activity logs.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any
12
+
13
+ from pydantic import Field, field_validator
14
+
15
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
16
+
17
+
18
+ # =============================================================================
19
+ # Enums
20
+ # =============================================================================
21
+
22
+
23
+ class ResourceType(str, Enum):
24
+ """Type of resource for comments and activities."""
25
+
26
+ TERM = "term"
27
+ CATEGORY = "category"
28
+ ASSET = "asset"
29
+ COLUMN = "column"
30
+
31
+
32
+ class ActivityAction(str, Enum):
33
+ """Type of activity action."""
34
+
35
+ CREATED = "created"
36
+ UPDATED = "updated"
37
+ DELETED = "deleted"
38
+ COMMENTED = "commented"
39
+ STATUS_CHANGED = "status_changed"
40
+ MAPPED = "mapped"
41
+ UNMAPPED = "unmapped"
42
+
43
+
44
+ # =============================================================================
45
+ # Comment Schemas
46
+ # =============================================================================
47
+
48
+
49
+ class CommentBase(BaseSchema):
50
+ """Base schema for comments."""
51
+
52
+ content: str = Field(..., min_length=1, max_length=10000, description="Comment content")
53
+
54
+
55
+ class CommentCreate(CommentBase):
56
+ """Schema for creating a comment."""
57
+
58
+ resource_type: ResourceType = Field(..., description="Type of resource")
59
+ resource_id: str = Field(..., description="ID of the resource")
60
+ parent_id: str | None = Field(None, description="Parent comment ID for replies")
61
+ author_id: str | None = Field(None, description="Author identifier")
62
+
63
+ @field_validator("content")
64
+ @classmethod
65
+ def validate_content(cls, v: str) -> str:
66
+ """Validate comment content."""
67
+ return v.strip()
68
+
69
+
70
+ class CommentUpdate(BaseSchema):
71
+ """Schema for updating a comment."""
72
+
73
+ content: str = Field(..., min_length=1, max_length=10000)
74
+
75
+
76
+ class CommentResponse(BaseSchema, IDMixin, TimestampMixin):
77
+ """Response schema for a comment."""
78
+
79
+ resource_type: ResourceType
80
+ resource_id: str
81
+ content: str
82
+ author_id: str | None
83
+ parent_id: str | None
84
+ is_reply: bool = False
85
+ reply_count: int = 0
86
+ replies: list[CommentResponse] = Field(default_factory=list)
87
+
88
+ @classmethod
89
+ def from_model(cls, comment: any, include_replies: bool = True) -> CommentResponse:
90
+ """Create response from model."""
91
+ replies = []
92
+ if include_replies and comment.replies:
93
+ replies = [
94
+ CommentResponse.from_model(r, include_replies=False)
95
+ for r in comment.replies
96
+ ]
97
+
98
+ return cls(
99
+ id=comment.id,
100
+ resource_type=ResourceType(comment.resource_type),
101
+ resource_id=comment.resource_id,
102
+ content=comment.content,
103
+ author_id=comment.author_id,
104
+ parent_id=comment.parent_id,
105
+ is_reply=comment.is_reply,
106
+ reply_count=comment.reply_count,
107
+ replies=replies,
108
+ created_at=comment.created_at,
109
+ updated_at=comment.updated_at,
110
+ )
111
+
112
+
113
+ class CommentListResponse(ListResponseWrapper[CommentResponse]):
114
+ """List of comments."""
115
+
116
+ pass
117
+
118
+
119
+ # =============================================================================
120
+ # Activity Schemas
121
+ # =============================================================================
122
+
123
+
124
+ class ActivityResponse(BaseSchema, IDMixin):
125
+ """Response schema for an activity log entry."""
126
+
127
+ resource_type: ResourceType
128
+ resource_id: str
129
+ action: ActivityAction
130
+ actor_id: str | None
131
+ description: str | None
132
+ metadata: dict[str, Any] | None = None
133
+ created_at: datetime
134
+
135
+ @classmethod
136
+ def from_model(cls, activity: any) -> ActivityResponse:
137
+ """Create response from model."""
138
+ return cls(
139
+ id=activity.id,
140
+ resource_type=ResourceType(activity.resource_type),
141
+ resource_id=activity.resource_id,
142
+ action=ActivityAction(activity.action),
143
+ actor_id=activity.actor_id,
144
+ description=activity.description,
145
+ metadata=activity.metadata,
146
+ created_at=activity.created_at,
147
+ )
148
+
149
+
150
+ class ActivityListResponse(ListResponseWrapper[ActivityResponse]):
151
+ """List of activities."""
152
+
153
+ pass
154
+
155
+
156
+ # =============================================================================
157
+ # Activity Create (Internal use)
158
+ # =============================================================================
159
+
160
+
161
+ class ActivityCreate(BaseSchema):
162
+ """Schema for creating an activity (internal use)."""
163
+
164
+ resource_type: ResourceType
165
+ resource_id: str
166
+ action: ActivityAction
167
+ actor_id: str | None = None
168
+ description: str | None = None
169
+ metadata: dict[str, Any] | None = None