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.
@@ -0,0 +1,349 @@
1
+ """Pydantic schemas for Business Glossary API.
2
+
3
+ This module defines request/response schemas for glossary terms,
4
+ categories, and relationships.
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
+
16
+
17
+ # =============================================================================
18
+ # Enums
19
+ # =============================================================================
20
+
21
+
22
+ class TermStatus(str, Enum):
23
+ """Status of a glossary term."""
24
+
25
+ DRAFT = "draft"
26
+ APPROVED = "approved"
27
+ DEPRECATED = "deprecated"
28
+
29
+
30
+ class RelationshipType(str, Enum):
31
+ """Type of relationship between terms."""
32
+
33
+ SYNONYM = "synonym"
34
+ RELATED = "related"
35
+ PARENT = "parent"
36
+ CHILD = "child"
37
+
38
+
39
+ # =============================================================================
40
+ # Category Schemas
41
+ # =============================================================================
42
+
43
+
44
+ class CategoryBase(BaseSchema):
45
+ """Base schema for glossary categories."""
46
+
47
+ name: str = Field(..., min_length=1, max_length=255, description="Category name")
48
+ description: str | None = Field(None, description="Category description")
49
+ parent_id: str | None = Field(None, description="Parent category ID")
50
+
51
+
52
+ class CategoryCreate(CategoryBase):
53
+ """Schema for creating a glossary category."""
54
+
55
+ pass
56
+
57
+
58
+ class CategoryUpdate(BaseSchema):
59
+ """Schema for updating a glossary category."""
60
+
61
+ name: str | None = Field(None, min_length=1, max_length=255)
62
+ description: str | None = None
63
+ parent_id: str | None = None
64
+
65
+
66
+ class CategorySummary(BaseSchema, IDMixin):
67
+ """Summary schema for category references."""
68
+
69
+ name: str
70
+ full_path: str | None = None
71
+
72
+
73
+ class CategoryResponse(BaseSchema, IDMixin, TimestampMixin):
74
+ """Response schema for a glossary category."""
75
+
76
+ name: str
77
+ description: str | None
78
+ parent_id: str | None
79
+ parent: CategorySummary | None = None
80
+ term_count: int = 0
81
+ full_path: str
82
+
83
+ @classmethod
84
+ def from_model(cls, category: any) -> CategoryResponse:
85
+ """Create response from model."""
86
+ parent_summary = None
87
+ if category.parent:
88
+ parent_summary = CategorySummary(
89
+ id=category.parent.id,
90
+ name=category.parent.name,
91
+ full_path=category.parent.full_path,
92
+ )
93
+ return cls(
94
+ id=category.id,
95
+ name=category.name,
96
+ description=category.description,
97
+ parent_id=category.parent_id,
98
+ parent=parent_summary,
99
+ term_count=category.term_count,
100
+ full_path=category.full_path,
101
+ created_at=category.created_at,
102
+ updated_at=category.updated_at,
103
+ )
104
+
105
+
106
+ class CategoryListResponse(ListResponseWrapper[CategoryResponse]):
107
+ """Paginated list of categories."""
108
+
109
+ pass
110
+
111
+
112
+ # =============================================================================
113
+ # Term Schemas
114
+ # =============================================================================
115
+
116
+
117
+ class TermBase(BaseSchema):
118
+ """Base schema for glossary terms."""
119
+
120
+ name: str = Field(..., min_length=1, max_length=255, description="Term name")
121
+ definition: str = Field(..., min_length=1, description="Term definition")
122
+ category_id: str | None = Field(None, description="Category ID")
123
+ status: TermStatus = Field(default=TermStatus.DRAFT, description="Term status")
124
+ owner_id: str | None = Field(None, description="Owner identifier")
125
+
126
+
127
+ class TermCreate(TermBase):
128
+ """Schema for creating a glossary term."""
129
+
130
+ @field_validator("name")
131
+ @classmethod
132
+ def validate_name(cls, v: str) -> str:
133
+ """Validate term name."""
134
+ return v.strip()
135
+
136
+ @field_validator("definition")
137
+ @classmethod
138
+ def validate_definition(cls, v: str) -> str:
139
+ """Validate term definition."""
140
+ return v.strip()
141
+
142
+
143
+ class TermUpdate(BaseSchema):
144
+ """Schema for updating a glossary term."""
145
+
146
+ name: str | None = Field(None, min_length=1, max_length=255)
147
+ definition: str | None = Field(None, min_length=1)
148
+ category_id: str | None = None
149
+ status: TermStatus | None = None
150
+ owner_id: str | None = None
151
+
152
+
153
+ class TermSummary(BaseSchema, IDMixin):
154
+ """Summary schema for term references."""
155
+
156
+ name: str
157
+ status: TermStatus
158
+
159
+
160
+ class RelatedTermSummary(BaseSchema, IDMixin):
161
+ """Summary for related terms with relationship type."""
162
+
163
+ name: str
164
+ status: TermStatus
165
+ relationship_type: RelationshipType
166
+
167
+
168
+ class TermResponse(BaseSchema, IDMixin, TimestampMixin):
169
+ """Response schema for a glossary term."""
170
+
171
+ name: str
172
+ definition: str
173
+ category_id: str | None
174
+ category: CategorySummary | None = None
175
+ status: TermStatus
176
+ owner_id: str | None
177
+ synonyms: list[TermSummary] = Field(default_factory=list)
178
+ related_terms: list[TermSummary] = Field(default_factory=list)
179
+ mapped_column_count: int = 0
180
+
181
+ @classmethod
182
+ def from_model(cls, term: any) -> TermResponse:
183
+ """Create response from model."""
184
+ category_summary = None
185
+ if term.category:
186
+ category_summary = CategorySummary(
187
+ id=term.category.id,
188
+ name=term.category.name,
189
+ full_path=term.category.full_path,
190
+ )
191
+
192
+ synonyms = [
193
+ TermSummary(id=t.id, name=t.name, status=TermStatus(t.status))
194
+ for t in term.synonyms
195
+ ]
196
+
197
+ related = [
198
+ TermSummary(id=t.id, name=t.name, status=TermStatus(t.status))
199
+ for t in term.related_terms
200
+ ]
201
+
202
+ return cls(
203
+ id=term.id,
204
+ name=term.name,
205
+ definition=term.definition,
206
+ category_id=term.category_id,
207
+ category=category_summary,
208
+ status=TermStatus(term.status),
209
+ owner_id=term.owner_id,
210
+ synonyms=synonyms,
211
+ related_terms=related,
212
+ mapped_column_count=len(term.mapped_columns),
213
+ created_at=term.created_at,
214
+ updated_at=term.updated_at,
215
+ )
216
+
217
+
218
+ class TermListItem(BaseSchema, IDMixin, TimestampMixin):
219
+ """List item schema for terms (lighter than full response)."""
220
+
221
+ name: str
222
+ definition: str
223
+ category_id: str | None
224
+ category_name: str | None = None
225
+ status: TermStatus
226
+ owner_id: str | None
227
+ synonym_count: int = 0
228
+ related_count: int = 0
229
+
230
+ @classmethod
231
+ def from_model(cls, term: any) -> TermListItem:
232
+ """Create list item from model."""
233
+ return cls(
234
+ id=term.id,
235
+ name=term.name,
236
+ definition=term.definition,
237
+ category_id=term.category_id,
238
+ category_name=term.category.name if term.category else None,
239
+ status=TermStatus(term.status),
240
+ owner_id=term.owner_id,
241
+ synonym_count=len(term.synonyms),
242
+ related_count=len(term.related_terms),
243
+ created_at=term.created_at,
244
+ updated_at=term.updated_at,
245
+ )
246
+
247
+
248
+ class TermListResponse(ListResponseWrapper[TermListItem]):
249
+ """Paginated list of terms."""
250
+
251
+ pass
252
+
253
+
254
+ # =============================================================================
255
+ # Relationship Schemas
256
+ # =============================================================================
257
+
258
+
259
+ class RelationshipBase(BaseSchema):
260
+ """Base schema for term relationships."""
261
+
262
+ source_term_id: str = Field(..., description="Source term ID")
263
+ target_term_id: str = Field(..., description="Target term ID")
264
+ relationship_type: RelationshipType = Field(..., description="Relationship type")
265
+
266
+
267
+ class RelationshipCreate(RelationshipBase):
268
+ """Schema for creating a term relationship."""
269
+
270
+ @field_validator("target_term_id")
271
+ @classmethod
272
+ def validate_different_terms(cls, v: str, info) -> str:
273
+ """Validate that source and target are different."""
274
+ if info.data.get("source_term_id") == v:
275
+ raise ValueError("Source and target terms must be different")
276
+ return v
277
+
278
+
279
+ class RelationshipResponse(BaseSchema, IDMixin):
280
+ """Response schema for a term relationship."""
281
+
282
+ source_term_id: str
283
+ target_term_id: str
284
+ source_term: TermSummary
285
+ target_term: TermSummary
286
+ relationship_type: RelationshipType
287
+ created_at: datetime
288
+
289
+ @classmethod
290
+ def from_model(cls, rel: any) -> RelationshipResponse:
291
+ """Create response from model."""
292
+ return cls(
293
+ id=rel.id,
294
+ source_term_id=rel.source_term_id,
295
+ target_term_id=rel.target_term_id,
296
+ source_term=TermSummary(
297
+ id=rel.source_term.id,
298
+ name=rel.source_term.name,
299
+ status=TermStatus(rel.source_term.status),
300
+ ),
301
+ target_term=TermSummary(
302
+ id=rel.target_term.id,
303
+ name=rel.target_term.name,
304
+ status=TermStatus(rel.target_term.status),
305
+ ),
306
+ relationship_type=RelationshipType(rel.relationship_type),
307
+ created_at=rel.created_at,
308
+ )
309
+
310
+
311
+ class RelationshipListResponse(ListResponseWrapper[RelationshipResponse]):
312
+ """List of relationships."""
313
+
314
+ pass
315
+
316
+
317
+ # =============================================================================
318
+ # History Schemas
319
+ # =============================================================================
320
+
321
+
322
+ class TermHistoryResponse(BaseSchema, IDMixin):
323
+ """Response schema for term change history."""
324
+
325
+ term_id: str
326
+ field_name: str
327
+ old_value: str | None
328
+ new_value: str | None
329
+ changed_by: str | None
330
+ changed_at: datetime
331
+
332
+ @classmethod
333
+ def from_model(cls, history: any) -> TermHistoryResponse:
334
+ """Create response from model."""
335
+ return cls(
336
+ id=history.id,
337
+ term_id=history.term_id,
338
+ field_name=history.field_name,
339
+ old_value=history.old_value,
340
+ new_value=history.new_value,
341
+ changed_by=history.changed_by,
342
+ changed_at=history.changed_at,
343
+ )
344
+
345
+
346
+ class TermHistoryListResponse(ListResponseWrapper[TermHistoryResponse]):
347
+ """List of term history entries."""
348
+
349
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: truthound-dashboard
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Open-source data quality dashboard - GX Cloud alternative
5
5
  Author-email: Truthound Team <team@truthound.dev>
6
6
  License-Expression: Apache-2.0
@@ -75,6 +75,8 @@ truthound-dashboard provides a graphical interface for managing data sources, ex
75
75
  | Webhook Notifications | Available | Available |
76
76
  | Drift Detection | Available | Available |
77
77
  | Data Profiling | Available | Available |
78
+ | Business Glossary | Available | Available |
79
+ | Data Catalog | Available | Available |
78
80
  | Dark Mode | Available | Available |
79
81
  | Multi-language (en/ko) | Not Available | Available |
80
82
  | License | Commercial | Apache 2.0 |
@@ -147,6 +149,24 @@ The dashboard interface is accessible at `http://localhost:8765`.
147
149
  - Statistical profiling using `th.profile`
148
150
  - Column-level statistics
149
151
 
152
+ ### Business Glossary
153
+ - Business term definitions with categories
154
+ - Term relationships (synonyms, related terms)
155
+ - Term lifecycle management (draft, approved, deprecated)
156
+ - Change history tracking
157
+
158
+ ### Data Catalog
159
+ - Data asset registration (tables, files, APIs)
160
+ - Column-level metadata management
161
+ - Column-to-term mapping
162
+ - Quality score tracking
163
+ - Sensitivity classification (public, internal, confidential, restricted)
164
+ - Custom tagging
165
+
166
+ ### Collaboration
167
+ - Comments on terms, assets, and columns
168
+ - Activity feed for tracking changes
169
+
150
170
  ### User Interface
151
171
  - Light and dark theme support
152
172
  - Internationalization: English, Korean
@@ -4,21 +4,24 @@ truthound_dashboard/cli.py,sha256=1x7AN2g9H5pZ4sG2PakCpSdBYnviknJv7LRIauAPzgU,19
4
4
  truthound_dashboard/config.py,sha256=fBSxoGitTe-9SeKkkQHtqNTcVxws5fvJ0YhF0EMuqG4,4089
5
5
  truthound_dashboard/main.py,sha256=tfwmNCRZ2RBub68KwAvO6a6d9LJB-S5YwZtjR6X0ywk,8129
6
6
  truthound_dashboard/api/__init__.py,sha256=rnbuYRDgh0GcqkR-d9Trkc8L0O34K4tMH6flyeQ9U98,361
7
+ truthound_dashboard/api/catalog.py,sha256=qQlsM4ubzjvI6rXwSY5214C-W93Ai-Xu1YjGVs1OoxE,10928
8
+ truthound_dashboard/api/collaboration.py,sha256=DxfpdUl_e8NfvDzv6-uwR32NN3Us7lLkzcif8RhvKyg,4720
7
9
  truthound_dashboard/api/deps.py,sha256=RqfEi6tp0OO9n5_Sh6QqoiiS4Gr7z77VMS-M6gEcbUY,3700
8
10
  truthound_dashboard/api/drift.py,sha256=pqpifxAifYVy2xr5EmGtLqZHfYYJ5exvrpWoB9eSEWo,5682
9
11
  truthound_dashboard/api/error_handlers.py,sha256=agovu0-CgMevgSvHh2aQnPjFpu1Wul_e67Vh9FAX_vM,8682
12
+ truthound_dashboard/api/glossary.py,sha256=Zdks7iHC9OLovgMJ28TELaET74OTQmUjP2TEwRLC8K4,10705
10
13
  truthound_dashboard/api/health.py,sha256=84UPWmnS-9Fqs39SyRD2gu4QPE3ZJkaqkp5qCHkizsw,1927
11
14
  truthound_dashboard/api/history.py,sha256=b6fy1pZfuwBqk3Yfeqnu8kaQcdNgWFMT7-0_29jLGOI,1792
12
15
  truthound_dashboard/api/middleware.py,sha256=4oqRCT6UhY-7HwoCy96b3vhaO7XBSrCW912IxAoDm-Q,19598
13
16
  truthound_dashboard/api/notifications.py,sha256=U1_VvgzAI3x5aKRg3zces1UwpbN9g-Tt6WFaOJziNfc,16800
14
17
  truthound_dashboard/api/profile.py,sha256=VtBpYGXuY1KbksWAornAVvrHsFnMjFXuKiUb6qp8Q3A,1389
15
- truthound_dashboard/api/router.py,sha256=abwYifmVaW6nUY-GyDvvQfqjkHQ9C82OHz6OK4Sqik0,1397
18
+ truthound_dashboard/api/router.py,sha256=ygiG6SzTETRQt5ystM1vJ2W-hDOwbpAt_ZzzRYyYKj0,2067
16
19
  truthound_dashboard/api/rules.py,sha256=6xcE5g7w9e_mt4M_XS7egIa16zcynYmXctmyayAKICs,7453
17
20
  truthound_dashboard/api/schedules.py,sha256=NMLU0szgZ2lGvVGuSJuEACZvc2qoeN65H-d2ANprj6E,8716
18
21
  truthound_dashboard/api/schemas.py,sha256=zkx3RNffRxtON-XUo1Xu5h13eIg63qs5pLdcM5atHuQ,3759
19
22
  truthound_dashboard/api/sources.py,sha256=uerDNqqQ4OVua_JbsDVS0fMMis-Q09mwQf6FiiOqRPU,5967
20
23
  truthound_dashboard/api/validations.py,sha256=D-8OyV8SuEAphTBhXEyclEZO_Jc-CjQ3ZqgZhXZLWHg,3557
21
- truthound_dashboard/core/__init__.py,sha256=gTKyzGPV8tfT6aFcLAC9bK0p4LKWjoq1yw3w1gtA5ME,6581
24
+ truthound_dashboard/core/__init__.py,sha256=AZryDS-BFT-LGsSJVqKXfe1iahf3AUsdpPFq2VgJVe8,6829
22
25
  truthound_dashboard/core/base.py,sha256=Jp2NFNCacIt2DStGOSvyUYnx1wV-FqDkX9gLroCbo5I,5293
23
26
  truthound_dashboard/core/cache.py,sha256=AP_7S5mK44vpQaTn5j2caQ5zumVdiQ7mICA4lIeK9y8,13937
24
27
  truthound_dashboard/core/connections.py,sha256=tytG1eyvRDXtfxIHvzInMAKtvZXYjXWgCvtrACH_RAk,9997
@@ -36,14 +39,22 @@ truthound_dashboard/core/notifications/channels.py,sha256=Z2TNrW81u2QJWvhd4Z6n2m
36
39
  truthound_dashboard/core/notifications/dispatcher.py,sha256=GXwqcss8_jnJL68BDaLy1Ld7OkLh4TiO3zj0Zf4l9_M,14077
37
40
  truthound_dashboard/core/notifications/events.py,sha256=mIkbW4m_8ofj1oYxm7o5FAxxJzP7_SIAz8WB1SeRdm4,4913
38
41
  truthound_dashboard/core/notifications/service.py,sha256=sPgfT-kow_gHoN9FaTuWXLXjo_LLwzJogelNPv5GiHM,21169
39
- truthound_dashboard/db/__init__.py,sha256=L4kwPQfFPw5rT0X7q-6V6zSHYDYgVpAB4Ti7SEJPZVc,1419
42
+ truthound_dashboard/core/phase5/__init__.py,sha256=-TNyj28lQC1YjoUtBBJj3ztksN65820Y-BWZRGIEOWA,447
43
+ truthound_dashboard/core/phase5/activity.py,sha256=GFCh4eU7ovDKUC_nE4j7Rk5CdxVLeUoF50A3_a6MvJk,4272
44
+ truthound_dashboard/core/phase5/catalog.py,sha256=hLVaIHxe9BxNp2Tdf7Ru-IIH-xDqUOOitN17zQmLuis,24241
45
+ truthound_dashboard/core/phase5/collaboration.py,sha256=5c2UXVZDGGTkAZ3N3onagNof-hza31tE1ak8t5uM4o0,8331
46
+ truthound_dashboard/core/phase5/glossary.py,sha256=6v3hYXpCQTZ5xaFwBM0IrXMiOeW4giNldHIjdY88ROA,24751
47
+ truthound_dashboard/db/__init__.py,sha256=e9jfHGvyNErb1RnA5x5ZXCIEenXxLjROP_yxQAu9oZk,2169
40
48
  truthound_dashboard/db/base.py,sha256=nj6DbYxAl5JoY0hC5rtM7pshOKpe-OH-AFsQqPGQzsM,2832
41
49
  truthound_dashboard/db/database.py,sha256=yQ9FsQSi29HvvW0t6BrkyX5YcHijRJEjGTTGfcCCw10,5078
42
- truthound_dashboard/db/models.py,sha256=spBx2_pWKutb-H6H-c31ByQvpS4kNmhTXA5kTvOtWME,25742
50
+ truthound_dashboard/db/models.py,sha256=rlVOcDCgHbup_L1wH6D9FICc_ZcVyHaQDYT_94s6LZY,47138
43
51
  truthound_dashboard/db/repository.py,sha256=48AJd65rwTSt1XqTN4vUeJ8nk416buE254JqipDCEEE,6890
44
- truthound_dashboard/schemas/__init__.py,sha256=8TQsBDD6X3vFspkz8s4qvbW9QPdjiGkYCDiUKQOdEUs,3156
52
+ truthound_dashboard/schemas/__init__.py,sha256=V-nrFHeKTSY55X6KyV2x0f0_-NgDLYrq8YeN8hlsb0Q,5546
45
53
  truthound_dashboard/schemas/base.py,sha256=lJUWh47Lz4E4sBGQDSS8jIfuSsTDbfxuOxvAMsvxfOg,2977
54
+ truthound_dashboard/schemas/catalog.py,sha256=Gc0ky7kuWyo7naJqyyJe-S9JDF5Ge_c9oeVpUEarQqw,10556
55
+ truthound_dashboard/schemas/collaboration.py,sha256=llFr14sm634pbqR9db4l8wH979jn2VpVUmi6DJ6dtHk,4864
46
56
  truthound_dashboard/schemas/drift.py,sha256=84jUqv6eTwdRlTwA1ZHf_BgnZej4gImyMcYEmAL3dDU,4361
57
+ truthound_dashboard/schemas/glossary.py,sha256=pY35k4zIf_4FYsdElD5fZJdJsPPQCn0Kk-EdwF8OCmI,10240
47
58
  truthound_dashboard/schemas/history.py,sha256=OVzBmbw7uz0C4IWt43P8QxNl9_OO_-WZRdA7L44_9yo,2771
48
59
  truthound_dashboard/schemas/profile.py,sha256=doM_RadBzXW5nt3_jus1AcExSjqa0BUYrE-qJb-GI_s,2920
49
60
  truthound_dashboard/schemas/rule.py,sha256=eIx9CdQ-gC6i-BpVsX714y-l0-TWY6VOVr0nSTkn_bk,5207
@@ -67,8 +78,8 @@ truthound_dashboard/translate/providers/mistral.py,sha256=j0oh_mGksdMuIfbuZKq0yo
67
78
  truthound_dashboard/translate/providers/ollama.py,sha256=XlAHE14VvdSPKFbrJIbB3KUHzrgQm0Zj08snL71otUQ,7286
68
79
  truthound_dashboard/translate/providers/openai.py,sha256=AeaOfRjNgCIgBdcX1gcYqqf41fXeEIyN3AiLAOy3SZc,6760
69
80
  truthound_dashboard/translate/providers/registry.py,sha256=iwfcWYJ2uKfwWjsalhV4jSoyZC7bSeujK6sTMuylMMY,6474
70
- truthound_dashboard-1.1.0.dist-info/METADATA,sha256=cQtjDqPX43DuUrf6ahIiIk2bf3OGpJQPYiAS78e_QsI,10494
71
- truthound_dashboard-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
72
- truthound_dashboard-1.1.0.dist-info/entry_points.txt,sha256=Xq8qadJ-Sqk4_0Ss_rhCqCv7uxPZZdwO3WUnbK0r6Hw,135
73
- truthound_dashboard-1.1.0.dist-info/licenses/LICENSE,sha256=qrBWTDMS8ZvwVJl3Yo2n8_AxOos3s8S9pcOzLW5Nivg,10763
74
- truthound_dashboard-1.1.0.dist-info/RECORD,,
81
+ truthound_dashboard-1.2.0.dist-info/METADATA,sha256=zXyFedj1mi9MsRFA150nzYTGciHnDGk1bWNVQ3c40IA,11118
82
+ truthound_dashboard-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
83
+ truthound_dashboard-1.2.0.dist-info/entry_points.txt,sha256=Xq8qadJ-Sqk4_0Ss_rhCqCv7uxPZZdwO3WUnbK0r6Hw,135
84
+ truthound_dashboard-1.2.0.dist-info/licenses/LICENSE,sha256=qrBWTDMS8ZvwVJl3Yo2n8_AxOos3s8S9pcOzLW5Nivg,10763
85
+ truthound_dashboard-1.2.0.dist-info/RECORD,,