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
@@ -2,19 +2,132 @@
2
2
 
3
3
  Provides utilities for building connection strings and testing connections
4
4
  for various database types supported by truthound.
5
+
6
+ Supported Source Types:
7
+ - file: CSV, Parquet, JSON, Excel files
8
+ - postgresql: PostgreSQL database
9
+ - mysql: MySQL database
10
+ - sqlite: SQLite database
11
+ - snowflake: Snowflake data warehouse
12
+ - bigquery: Google BigQuery
13
+ - redshift: Amazon Redshift
14
+ - databricks: Databricks (Unity Catalog / Delta Lake)
15
+ - oracle: Oracle Database
16
+ - sqlserver: Microsoft SQL Server
17
+ - spark: Apache Spark (via JDBC or Hive)
5
18
  """
6
19
 
7
20
  from __future__ import annotations
8
21
 
9
22
  from abc import ABC, abstractmethod
23
+ from dataclasses import dataclass, field
24
+ from enum import Enum
10
25
  from pathlib import Path
11
- from typing import Any
26
+ from typing import Any, Literal
12
27
  from urllib.parse import quote_plus
13
28
 
14
29
 
30
+ class SourceType(str, Enum):
31
+ """Supported data source types."""
32
+
33
+ FILE = "file"
34
+ POSTGRESQL = "postgresql"
35
+ MYSQL = "mysql"
36
+ SQLITE = "sqlite"
37
+ SNOWFLAKE = "snowflake"
38
+ BIGQUERY = "bigquery"
39
+ REDSHIFT = "redshift"
40
+ DATABRICKS = "databricks"
41
+ ORACLE = "oracle"
42
+ SQLSERVER = "sqlserver"
43
+ SPARK = "spark"
44
+
45
+
46
+ class FieldType(str, Enum):
47
+ """Field types for configuration forms."""
48
+
49
+ TEXT = "text"
50
+ PASSWORD = "password"
51
+ NUMBER = "number"
52
+ SELECT = "select"
53
+ BOOLEAN = "boolean"
54
+ FILE_PATH = "file_path"
55
+ TEXTAREA = "textarea"
56
+
57
+
58
+ @dataclass
59
+ class FieldDefinition:
60
+ """Definition of a configuration field for UI rendering."""
61
+
62
+ name: str
63
+ label: str
64
+ type: FieldType = FieldType.TEXT
65
+ required: bool = False
66
+ placeholder: str = ""
67
+ description: str = ""
68
+ default: Any = None
69
+ options: list[dict[str, str]] = field(default_factory=list)
70
+ min_value: int | None = None
71
+ max_value: int | None = None
72
+ depends_on: str | None = None
73
+ depends_value: Any = None
74
+
75
+ def to_dict(self) -> dict[str, Any]:
76
+ """Convert to dictionary for API response."""
77
+ result = {
78
+ "name": self.name,
79
+ "label": self.label,
80
+ "type": self.type.value,
81
+ "required": self.required,
82
+ "placeholder": self.placeholder,
83
+ "description": self.description,
84
+ }
85
+ if self.default is not None:
86
+ result["default"] = self.default
87
+ if self.options:
88
+ result["options"] = self.options
89
+ if self.min_value is not None:
90
+ result["min_value"] = self.min_value
91
+ if self.max_value is not None:
92
+ result["max_value"] = self.max_value
93
+ if self.depends_on:
94
+ result["depends_on"] = self.depends_on
95
+ result["depends_value"] = self.depends_value
96
+ return result
97
+
98
+
99
+ @dataclass
100
+ class SourceTypeDefinition:
101
+ """Complete definition of a source type for UI rendering."""
102
+
103
+ type: str
104
+ name: str
105
+ description: str
106
+ icon: str
107
+ category: Literal["file", "database", "warehouse", "bigdata"]
108
+ fields: list[FieldDefinition]
109
+ docs_url: str = ""
110
+
111
+ def to_dict(self) -> dict[str, Any]:
112
+ """Convert to dictionary for API response."""
113
+ return {
114
+ "type": self.type,
115
+ "name": self.name,
116
+ "description": self.description,
117
+ "icon": self.icon,
118
+ "category": self.category,
119
+ "fields": [f.to_dict() for f in self.fields],
120
+ "required_fields": [f.name for f in self.fields if f.required],
121
+ "optional_fields": [f.name for f in self.fields if not f.required],
122
+ "docs_url": self.docs_url,
123
+ }
124
+
125
+
15
126
  class ConnectionBuilder(ABC):
16
127
  """Abstract base class for connection string builders."""
17
128
 
129
+ source_type: SourceType
130
+
18
131
  @abstractmethod
19
132
  def build(self, config: dict[str, Any]) -> str:
20
133
  """Build connection string from configuration.
@@ -42,10 +155,17 @@ class ConnectionBuilder(ABC):
42
155
  """
43
156
  pass
44
157
 
158
+ @classmethod
159
+ @abstractmethod
160
+ def get_definition(cls) -> SourceTypeDefinition:
161
+ """Get the source type definition for UI rendering."""
162
+ pass
163
+
45
164
 
46
165
  class FileConnectionBuilder(ConnectionBuilder):
47
166
  """Connection builder for file-based sources."""
48
167
 
168
+ source_type = SourceType.FILE
49
169
  SUPPORTED_EXTENSIONS = {".csv", ".parquet", ".json", ".xlsx", ".xls"}
50
170
 
51
171
  def build(self, config: dict[str, Any]) -> str:
@@ -69,10 +189,84 @@ class FileConnectionBuilder(ConnectionBuilder):
69
189
 
70
190
  return errors
71
191
 
192
+ @classmethod
193
+ def get_definition(cls) -> SourceTypeDefinition:
194
+ """Get the source type definition for UI rendering."""
195
+ return SourceTypeDefinition(
196
+ type=SourceType.FILE.value,
197
+ name="File",
198
+ description="Local file (CSV, Parquet, JSON, Excel)",
199
+ icon="file",
200
+ category="file",
201
+ fields=[
202
+ FieldDefinition(
203
+ name="path",
204
+ label="File Path",
205
+ type=FieldType.FILE_PATH,
206
+ required=True,
207
+ placeholder="/path/to/data.csv",
208
+ description="Path to the data file",
209
+ ),
210
+ FieldDefinition(
211
+ name="format",
212
+ label="Format",
213
+ type=FieldType.SELECT,
214
+ options=[
215
+ {"value": "auto", "label": "Auto-detect"},
216
+ {"value": "csv", "label": "CSV"},
217
+ {"value": "parquet", "label": "Parquet"},
218
+ {"value": "json", "label": "JSON"},
219
+ {"value": "excel", "label": "Excel"},
220
+ ],
221
+ default="auto",
222
+ description="File format (auto-detected from extension if not specified)",
223
+ ),
224
+ FieldDefinition(
225
+ name="delimiter",
226
+ label="Delimiter",
227
+ placeholder=",",
228
+ default=",",
229
+ description="CSV delimiter character",
230
+ depends_on="format",
231
+ depends_value="csv",
232
+ ),
233
+ FieldDefinition(
234
+ name="encoding",
235
+ label="Encoding",
236
+ type=FieldType.SELECT,
237
+ options=[
238
+ {"value": "utf-8", "label": "UTF-8"},
239
+ {"value": "utf-16", "label": "UTF-16"},
240
+ {"value": "iso-8859-1", "label": "ISO-8859-1 (Latin-1)"},
241
+ {"value": "cp1252", "label": "Windows-1252"},
242
+ ],
243
+ default="utf-8",
244
+ description="File encoding",
245
+ ),
246
+ FieldDefinition(
247
+ name="has_header",
248
+ label="Has Header Row",
249
+ type=FieldType.BOOLEAN,
250
+ default=True,
251
+ description="First row contains column names",
252
+ ),
253
+ FieldDefinition(
254
+ name="sheet",
255
+ label="Sheet Name",
256
+ placeholder="Sheet1",
257
+ description="Excel sheet name (for Excel files)",
258
+ depends_on="format",
259
+ depends_value="excel",
260
+ ),
261
+ ],
262
+ )
263
+
72
264
 
73
265
  class PostgreSQLConnectionBuilder(ConnectionBuilder):
74
266
  """Connection builder for PostgreSQL databases."""
75
267
 
268
+ source_type = SourceType.POSTGRESQL
269
+
76
270
  def build(self, config: dict[str, Any]) -> str:
77
271
  """Build PostgreSQL connection string."""
78
272
  host = config.get("host", "localhost")
@@ -100,10 +294,88 @@ class PostgreSQLConnectionBuilder(ConnectionBuilder):
100
294
 
101
295
  return errors
102
296
 
297
+ @classmethod
298
+ def get_definition(cls) -> SourceTypeDefinition:
299
+ """Get the source type definition for UI rendering."""
300
+ return SourceTypeDefinition(
301
+ type=SourceType.POSTGRESQL.value,
302
+ name="PostgreSQL",
303
+ description="PostgreSQL database",
304
+ icon="database",
305
+ category="database",
306
+ docs_url="https://www.postgresql.org/docs/",
307
+ fields=[
308
+ FieldDefinition(
309
+ name="host",
310
+ label="Host",
311
+ required=True,
312
+ placeholder="localhost",
313
+ description="Database server hostname or IP",
314
+ ),
315
+ FieldDefinition(
316
+ name="port",
317
+ label="Port",
318
+ type=FieldType.NUMBER,
319
+ default=5432,
320
+ min_value=1,
321
+ max_value=65535,
322
+ description="Database server port",
323
+ ),
324
+ FieldDefinition(
325
+ name="database",
326
+ label="Database",
327
+ required=True,
328
+ placeholder="mydb",
329
+ description="Database name",
330
+ ),
331
+ FieldDefinition(
332
+ name="username",
333
+ label="Username",
334
+ required=True,
335
+ placeholder="postgres",
336
+ description="Database username",
337
+ ),
338
+ FieldDefinition(
339
+ name="password",
340
+ label="Password",
341
+ type=FieldType.PASSWORD,
342
+ description="Database password",
343
+ ),
344
+ FieldDefinition(
345
+ name="schema",
346
+ label="Schema",
347
+ placeholder="public",
348
+ default="public",
349
+ description="Database schema",
350
+ ),
351
+ FieldDefinition(
352
+ name="table",
353
+ label="Table",
354
+ placeholder="my_table",
355
+ description="Table name to validate",
356
+ ),
357
+ FieldDefinition(
358
+ name="ssl_mode",
359
+ label="SSL Mode",
360
+ type=FieldType.SELECT,
361
+ options=[
362
+ {"value": "disable", "label": "Disable"},
363
+ {"value": "require", "label": "Require"},
364
+ {"value": "verify-ca", "label": "Verify CA"},
365
+ {"value": "verify-full", "label": "Verify Full"},
366
+ ],
367
+ default="disable",
368
+ description="SSL connection mode",
369
+ ),
370
+ ],
371
+ )
372
+
103
373
 
104
374
  class MySQLConnectionBuilder(ConnectionBuilder):
105
375
  """Connection builder for MySQL databases."""
106
376
 
377
+ source_type = SourceType.MYSQL
378
+
107
379
  def build(self, config: dict[str, Any]) -> str:
108
380
  """Build MySQL connection string."""
109
381
  host = config.get("host", "localhost")
@@ -125,10 +397,121 @@ class MySQLConnectionBuilder(ConnectionBuilder):
125
397
 
126
398
  return errors
127
399
 
400
+ @classmethod
401
+ def get_definition(cls) -> SourceTypeDefinition:
402
+ """Get the source type definition for UI rendering."""
403
+ return SourceTypeDefinition(
404
+ type=SourceType.MYSQL.value,
405
+ name="MySQL",
406
+ description="MySQL database",
407
+ icon="database",
408
+ category="database",
409
+ docs_url="https://dev.mysql.com/doc/",
410
+ fields=[
411
+ FieldDefinition(
412
+ name="host",
413
+ label="Host",
414
+ required=True,
415
+ placeholder="localhost",
416
+ description="Database server hostname or IP",
417
+ ),
418
+ FieldDefinition(
419
+ name="port",
420
+ label="Port",
421
+ type=FieldType.NUMBER,
422
+ default=3306,
423
+ min_value=1,
424
+ max_value=65535,
425
+ description="Database server port",
426
+ ),
427
+ FieldDefinition(
428
+ name="database",
429
+ label="Database",
430
+ required=True,
431
+ placeholder="mydb",
432
+ description="Database name",
433
+ ),
434
+ FieldDefinition(
435
+ name="username",
436
+ label="Username",
437
+ required=True,
438
+ placeholder="root",
439
+ description="Database username",
440
+ ),
441
+ FieldDefinition(
442
+ name="password",
443
+ label="Password",
444
+ type=FieldType.PASSWORD,
445
+ description="Database password",
446
+ ),
447
+ FieldDefinition(
448
+ name="table",
449
+ label="Table",
450
+ placeholder="my_table",
451
+ description="Table name to validate",
452
+ ),
453
+ FieldDefinition(
454
+ name="ssl",
455
+ label="Use SSL",
456
+ type=FieldType.BOOLEAN,
457
+ default=False,
458
+ description="Enable SSL connection",
459
+ ),
460
+ ],
461
+ )
462
+
463
+
464
+ class SQLiteConnectionBuilder(ConnectionBuilder):
465
+ """Connection builder for SQLite databases."""
466
+
467
+ source_type = SourceType.SQLITE
468
+
469
+ def build(self, config: dict[str, Any]) -> str:
470
+ """Build SQLite connection string."""
471
+ path = config.get("path", "")
472
+ return f"sqlite:///{path}"
473
+
474
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
475
+ """Validate SQLite configuration."""
476
+ errors = []
477
+ if not config.get("path"):
478
+ errors.append("path is required")
479
+ return errors
480
+
481
+ @classmethod
482
+ def get_definition(cls) -> SourceTypeDefinition:
483
+ """Get the source type definition for UI rendering."""
484
+ return SourceTypeDefinition(
485
+ type=SourceType.SQLITE.value,
486
+ name="SQLite",
487
+ description="SQLite database file",
488
+ icon="database",
489
+ category="database",
490
+ docs_url="https://www.sqlite.org/docs.html",
491
+ fields=[
492
+ FieldDefinition(
493
+ name="path",
494
+ label="Database Path",
495
+ type=FieldType.FILE_PATH,
496
+ required=True,
497
+ placeholder="/path/to/database.db",
498
+ description="Path to the SQLite database file",
499
+ ),
500
+ FieldDefinition(
501
+ name="table",
502
+ label="Table",
503
+ placeholder="my_table",
504
+ description="Table name to validate",
505
+ ),
506
+ ],
507
+ )
508
+
128
509
 
129
510
  class SnowflakeConnectionBuilder(ConnectionBuilder):
130
511
  """Connection builder for Snowflake databases."""
131
512
 
513
+ source_type = SourceType.SNOWFLAKE
514
+
132
515
  def build(self, config: dict[str, Any]) -> str:
133
516
  """Build Snowflake connection string."""
134
517
  account = config.get("account", "")
@@ -160,10 +543,78 @@ class SnowflakeConnectionBuilder(ConnectionBuilder):
160
543
 
161
544
  return errors
162
545
 
546
+ @classmethod
547
+ def get_definition(cls) -> SourceTypeDefinition:
548
+ """Get the source type definition for UI rendering."""
549
+ return SourceTypeDefinition(
550
+ type=SourceType.SNOWFLAKE.value,
551
+ name="Snowflake",
552
+ description="Snowflake data warehouse",
553
+ icon="snowflake",
554
+ category="warehouse",
555
+ docs_url="https://docs.snowflake.com/",
556
+ fields=[
557
+ FieldDefinition(
558
+ name="account",
559
+ label="Account",
560
+ required=True,
561
+ placeholder="xy12345.us-east-1",
562
+ description="Snowflake account identifier",
563
+ ),
564
+ FieldDefinition(
565
+ name="username",
566
+ label="Username",
567
+ required=True,
568
+ description="Snowflake username",
569
+ ),
570
+ FieldDefinition(
571
+ name="password",
572
+ label="Password",
573
+ type=FieldType.PASSWORD,
574
+ description="Snowflake password",
575
+ ),
576
+ FieldDefinition(
577
+ name="warehouse",
578
+ label="Warehouse",
579
+ required=True,
580
+ placeholder="COMPUTE_WH",
581
+ description="Snowflake warehouse name",
582
+ ),
583
+ FieldDefinition(
584
+ name="database",
585
+ label="Database",
586
+ required=True,
587
+ placeholder="MY_DB",
588
+ description="Database name",
589
+ ),
590
+ FieldDefinition(
591
+ name="schema",
592
+ label="Schema",
593
+ placeholder="PUBLIC",
594
+ default="PUBLIC",
595
+ description="Schema name",
596
+ ),
597
+ FieldDefinition(
598
+ name="role",
599
+ label="Role",
600
+ placeholder="ACCOUNTADMIN",
601
+ description="Snowflake role (optional)",
602
+ ),
603
+ FieldDefinition(
604
+ name="table",
605
+ label="Table",
606
+ placeholder="MY_TABLE",
607
+ description="Table name to validate",
608
+ ),
609
+ ],
610
+ )
611
+
163
612
 
164
613
  class BigQueryConnectionBuilder(ConnectionBuilder):
165
614
  """Connection builder for BigQuery."""
166
615
 
616
+ source_type = SourceType.BIGQUERY
617
+
167
618
  def build(self, config: dict[str, Any]) -> str:
168
619
  """Build BigQuery connection string."""
169
620
  project = config.get("project", "")
@@ -184,14 +635,579 @@ class BigQueryConnectionBuilder(ConnectionBuilder):
184
635
 
185
636
  return errors
186
637
 
638
+ @classmethod
639
+ def get_definition(cls) -> SourceTypeDefinition:
640
+ """Get the source type definition for UI rendering."""
641
+ return SourceTypeDefinition(
642
+ type=SourceType.BIGQUERY.value,
643
+ name="BigQuery",
644
+ description="Google BigQuery",
645
+ icon="cloud",
646
+ category="warehouse",
647
+ docs_url="https://cloud.google.com/bigquery/docs",
648
+ fields=[
649
+ FieldDefinition(
650
+ name="project",
651
+ label="Project ID",
652
+ required=True,
653
+ placeholder="my-gcp-project",
654
+ description="Google Cloud project ID",
655
+ ),
656
+ FieldDefinition(
657
+ name="dataset",
658
+ label="Dataset",
659
+ placeholder="my_dataset",
660
+ description="BigQuery dataset name",
661
+ ),
662
+ FieldDefinition(
663
+ name="table",
664
+ label="Table",
665
+ placeholder="my_table",
666
+ description="Table name to validate",
667
+ ),
668
+ FieldDefinition(
669
+ name="location",
670
+ label="Location",
671
+ type=FieldType.SELECT,
672
+ options=[
673
+ {"value": "US", "label": "US (multi-region)"},
674
+ {"value": "EU", "label": "EU (multi-region)"},
675
+ {"value": "us-central1", "label": "Iowa (us-central1)"},
676
+ {"value": "us-east1", "label": "S. Carolina (us-east1)"},
677
+ {"value": "us-west1", "label": "Oregon (us-west1)"},
678
+ {"value": "europe-west1", "label": "Belgium (europe-west1)"},
679
+ {"value": "asia-east1", "label": "Taiwan (asia-east1)"},
680
+ {"value": "asia-northeast1", "label": "Tokyo (asia-northeast1)"},
681
+ ],
682
+ default="US",
683
+ description="Dataset location",
684
+ ),
685
+ FieldDefinition(
686
+ name="credentials_path",
687
+ label="Credentials File",
688
+ type=FieldType.FILE_PATH,
689
+ placeholder="/path/to/service-account.json",
690
+ description="Path to service account JSON key file",
691
+ ),
692
+ ],
693
+ )
694
+
695
+
696
+ class RedshiftConnectionBuilder(ConnectionBuilder):
697
+ """Connection builder for Amazon Redshift."""
698
+
699
+ source_type = SourceType.REDSHIFT
700
+
701
+ def build(self, config: dict[str, Any]) -> str:
702
+ """Build Redshift connection string."""
703
+ host = config.get("host", "")
704
+ port = config.get("port", 5439)
705
+ database = config.get("database", "")
706
+ username = config.get("username", "")
707
+ password = quote_plus(config.get("password", ""))
708
+ schema = config.get("schema", "public")
709
+
710
+ conn = f"redshift+psycopg2://{username}:{password}@{host}:{port}/{database}"
711
+ if schema:
712
+ conn += f"?options=-csearch_path%3D{schema}"
713
+
714
+ return conn
715
+
716
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
717
+ """Validate Redshift configuration."""
718
+ errors = []
719
+ required = ["host", "database", "username"]
720
+
721
+ for field in required:
722
+ if not config.get(field):
723
+ errors.append(f"{field} is required")
724
+
725
+ return errors
726
+
727
+ @classmethod
728
+ def get_definition(cls) -> SourceTypeDefinition:
729
+ """Get the source type definition for UI rendering."""
730
+ return SourceTypeDefinition(
731
+ type=SourceType.REDSHIFT.value,
732
+ name="Amazon Redshift",
733
+ description="Amazon Redshift data warehouse",
734
+ icon="cloud",
735
+ category="warehouse",
736
+ docs_url="https://docs.aws.amazon.com/redshift/",
737
+ fields=[
738
+ FieldDefinition(
739
+ name="host",
740
+ label="Host",
741
+ required=True,
742
+ placeholder="my-cluster.xxxxx.region.redshift.amazonaws.com",
743
+ description="Redshift cluster endpoint",
744
+ ),
745
+ FieldDefinition(
746
+ name="port",
747
+ label="Port",
748
+ type=FieldType.NUMBER,
749
+ default=5439,
750
+ min_value=1,
751
+ max_value=65535,
752
+ description="Redshift port (default: 5439)",
753
+ ),
754
+ FieldDefinition(
755
+ name="database",
756
+ label="Database",
757
+ required=True,
758
+ placeholder="dev",
759
+ description="Database name",
760
+ ),
761
+ FieldDefinition(
762
+ name="username",
763
+ label="Username",
764
+ required=True,
765
+ placeholder="admin",
766
+ description="Database username",
767
+ ),
768
+ FieldDefinition(
769
+ name="password",
770
+ label="Password",
771
+ type=FieldType.PASSWORD,
772
+ description="Database password",
773
+ ),
774
+ FieldDefinition(
775
+ name="schema",
776
+ label="Schema",
777
+ placeholder="public",
778
+ default="public",
779
+ description="Database schema",
780
+ ),
781
+ FieldDefinition(
782
+ name="table",
783
+ label="Table",
784
+ placeholder="my_table",
785
+ description="Table name to validate",
786
+ ),
787
+ FieldDefinition(
788
+ name="iam_role",
789
+ label="IAM Role ARN",
790
+ placeholder="arn:aws:iam::123456789:role/MyRole",
791
+ description="IAM role for S3 access (optional)",
792
+ ),
793
+ ],
794
+ )
795
+
796
+
797
+ class DatabricksConnectionBuilder(ConnectionBuilder):
798
+ """Connection builder for Databricks."""
799
+
800
+ source_type = SourceType.DATABRICKS
801
+
802
+ def build(self, config: dict[str, Any]) -> str:
803
+ """Build Databricks connection string."""
804
+ host = config.get("host", "")
805
+ http_path = config.get("http_path", "")
806
+ token = config.get("token", "")
807
+ catalog = config.get("catalog", "")
808
+ schema = config.get("schema", "default")
809
+
810
+ # Databricks SQL uses token-based auth
811
+ conn = f"databricks://token:{token}@{host}?http_path={http_path}"
812
+ if catalog:
813
+ conn += f"&catalog={catalog}"
814
+ if schema:
815
+ conn += f"&schema={schema}"
816
+
817
+ return conn
818
+
819
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
820
+ """Validate Databricks configuration."""
821
+ errors = []
822
+ required = ["host", "http_path", "token"]
823
+
824
+ for field in required:
825
+ if not config.get(field):
826
+ errors.append(f"{field} is required")
827
+
828
+ return errors
829
+
830
+ @classmethod
831
+ def get_definition(cls) -> SourceTypeDefinition:
832
+ """Get the source type definition for UI rendering."""
833
+ return SourceTypeDefinition(
834
+ type=SourceType.DATABRICKS.value,
835
+ name="Databricks",
836
+ description="Databricks (Unity Catalog / Delta Lake)",
837
+ icon="layers",
838
+ category="bigdata",
839
+ docs_url="https://docs.databricks.com/",
840
+ fields=[
841
+ FieldDefinition(
842
+ name="host",
843
+ label="Host",
844
+ required=True,
845
+ placeholder="adb-xxxxx.azuredatabricks.net",
846
+ description="Databricks workspace URL",
847
+ ),
848
+ FieldDefinition(
849
+ name="http_path",
850
+ label="HTTP Path",
851
+ required=True,
852
+ placeholder="/sql/1.0/warehouses/xxxxx",
853
+ description="SQL warehouse HTTP path",
854
+ ),
855
+ FieldDefinition(
856
+ name="token",
857
+ label="Access Token",
858
+ type=FieldType.PASSWORD,
859
+ required=True,
860
+ description="Databricks personal access token",
861
+ ),
862
+ FieldDefinition(
863
+ name="catalog",
864
+ label="Catalog",
865
+ placeholder="main",
866
+ description="Unity Catalog name",
867
+ ),
868
+ FieldDefinition(
869
+ name="schema",
870
+ label="Schema",
871
+ placeholder="default",
872
+ default="default",
873
+ description="Schema name",
874
+ ),
875
+ FieldDefinition(
876
+ name="table",
877
+ label="Table",
878
+ placeholder="my_table",
879
+ description="Table name to validate",
880
+ ),
881
+ ],
882
+ )
883
+
884
+
885
+ class OracleConnectionBuilder(ConnectionBuilder):
886
+ """Connection builder for Oracle Database."""
887
+
888
+ source_type = SourceType.ORACLE
889
+
890
+ def build(self, config: dict[str, Any]) -> str:
891
+ """Build Oracle connection string."""
892
+ host = config.get("host", "localhost")
893
+ port = config.get("port", 1521)
894
+ service_name = config.get("service_name", "")
895
+ sid = config.get("sid", "")
896
+ username = config.get("username", "")
897
+ password = quote_plus(config.get("password", ""))
898
+
899
+ # Oracle supports both SID and Service Name
900
+ if service_name:
901
+ return f"oracle+cx_oracle://{username}:{password}@{host}:{port}/?service_name={service_name}"
902
+ elif sid:
903
+ return f"oracle+cx_oracle://{username}:{password}@{host}:{port}/{sid}"
904
+ else:
905
+ return f"oracle+cx_oracle://{username}:{password}@{host}:{port}"
906
+
907
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
908
+ """Validate Oracle configuration."""
909
+ errors = []
910
+ required = ["host", "username"]
911
+
912
+ for field in required:
913
+ if not config.get(field):
914
+ errors.append(f"{field} is required")
915
+
916
+ # Either service_name or sid should be provided
917
+ if not config.get("service_name") and not config.get("sid"):
918
+ errors.append("Either service_name or sid is required")
919
+
920
+ return errors
921
+
922
+ @classmethod
923
+ def get_definition(cls) -> SourceTypeDefinition:
924
+ """Get the source type definition for UI rendering."""
925
+ return SourceTypeDefinition(
926
+ type=SourceType.ORACLE.value,
927
+ name="Oracle",
928
+ description="Oracle Database",
929
+ icon="database",
930
+ category="database",
931
+ docs_url="https://docs.oracle.com/en/database/",
932
+ fields=[
933
+ FieldDefinition(
934
+ name="host",
935
+ label="Host",
936
+ required=True,
937
+ placeholder="localhost",
938
+ description="Database server hostname or IP",
939
+ ),
940
+ FieldDefinition(
941
+ name="port",
942
+ label="Port",
943
+ type=FieldType.NUMBER,
944
+ default=1521,
945
+ min_value=1,
946
+ max_value=65535,
947
+ description="Oracle listener port",
948
+ ),
949
+ FieldDefinition(
950
+ name="service_name",
951
+ label="Service Name",
952
+ placeholder="ORCLPDB1",
953
+ description="Oracle service name (preferred over SID)",
954
+ ),
955
+ FieldDefinition(
956
+ name="sid",
957
+ label="SID",
958
+ placeholder="ORCL",
959
+ description="Oracle SID (legacy, use Service Name if possible)",
960
+ ),
961
+ FieldDefinition(
962
+ name="username",
963
+ label="Username",
964
+ required=True,
965
+ placeholder="SYSTEM",
966
+ description="Database username",
967
+ ),
968
+ FieldDefinition(
969
+ name="password",
970
+ label="Password",
971
+ type=FieldType.PASSWORD,
972
+ description="Database password",
973
+ ),
974
+ FieldDefinition(
975
+ name="table",
976
+ label="Table",
977
+ placeholder="MY_TABLE",
978
+ description="Table name to validate",
979
+ ),
980
+ ],
981
+ )
982
+
983
+
984
+ class SQLServerConnectionBuilder(ConnectionBuilder):
985
+ """Connection builder for Microsoft SQL Server."""
986
+
987
+ source_type = SourceType.SQLSERVER
988
+
989
+ def build(self, config: dict[str, Any]) -> str:
990
+ """Build SQL Server connection string."""
991
+ host = config.get("host", "localhost")
992
+ port = config.get("port", 1433)
993
+ database = config.get("database", "")
994
+ username = config.get("username", "")
995
+ password = quote_plus(config.get("password", ""))
996
+ schema = config.get("schema", "dbo")
997
+ driver = config.get("driver", "ODBC Driver 17 for SQL Server")
998
+
999
+ # Build connection string with URL-encoded driver
1000
+ encoded_driver = quote_plus(driver)
1001
+ conn = f"mssql+pyodbc://{username}:{password}@{host}:{port}/{database}?driver={encoded_driver}"
1002
+
1003
+ return conn
1004
+
1005
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
1006
+ """Validate SQL Server configuration."""
1007
+ errors = []
1008
+ required = ["host", "database", "username"]
1009
+
1010
+ for field in required:
1011
+ if not config.get(field):
1012
+ errors.append(f"{field} is required")
1013
+
1014
+ return errors
1015
+
1016
+ @classmethod
1017
+ def get_definition(cls) -> SourceTypeDefinition:
1018
+ """Get the source type definition for UI rendering."""
1019
+ return SourceTypeDefinition(
1020
+ type=SourceType.SQLSERVER.value,
1021
+ name="SQL Server",
1022
+ description="Microsoft SQL Server",
1023
+ icon="database",
1024
+ category="database",
1025
+ docs_url="https://docs.microsoft.com/en-us/sql/",
1026
+ fields=[
1027
+ FieldDefinition(
1028
+ name="host",
1029
+ label="Host",
1030
+ required=True,
1031
+ placeholder="localhost",
1032
+ description="SQL Server hostname or IP",
1033
+ ),
1034
+ FieldDefinition(
1035
+ name="port",
1036
+ label="Port",
1037
+ type=FieldType.NUMBER,
1038
+ default=1433,
1039
+ min_value=1,
1040
+ max_value=65535,
1041
+ description="SQL Server port",
1042
+ ),
1043
+ FieldDefinition(
1044
+ name="database",
1045
+ label="Database",
1046
+ required=True,
1047
+ placeholder="mydb",
1048
+ description="Database name",
1049
+ ),
1050
+ FieldDefinition(
1051
+ name="username",
1052
+ label="Username",
1053
+ required=True,
1054
+ placeholder="sa",
1055
+ description="SQL Server login",
1056
+ ),
1057
+ FieldDefinition(
1058
+ name="password",
1059
+ label="Password",
1060
+ type=FieldType.PASSWORD,
1061
+ description="SQL Server password",
1062
+ ),
1063
+ FieldDefinition(
1064
+ name="schema",
1065
+ label="Schema",
1066
+ placeholder="dbo",
1067
+ default="dbo",
1068
+ description="Database schema",
1069
+ ),
1070
+ FieldDefinition(
1071
+ name="table",
1072
+ label="Table",
1073
+ placeholder="my_table",
1074
+ description="Table name to validate",
1075
+ ),
1076
+ FieldDefinition(
1077
+ name="driver",
1078
+ label="ODBC Driver",
1079
+ type=FieldType.SELECT,
1080
+ options=[
1081
+ {"value": "ODBC Driver 17 for SQL Server", "label": "ODBC Driver 17"},
1082
+ {"value": "ODBC Driver 18 for SQL Server", "label": "ODBC Driver 18"},
1083
+ {"value": "SQL Server Native Client 11.0", "label": "Native Client 11.0"},
1084
+ ],
1085
+ default="ODBC Driver 17 for SQL Server",
1086
+ description="ODBC driver to use",
1087
+ ),
1088
+ FieldDefinition(
1089
+ name="trust_server_certificate",
1090
+ label="Trust Server Certificate",
1091
+ type=FieldType.BOOLEAN,
1092
+ default=False,
1093
+ description="Trust the server certificate without validation",
1094
+ ),
1095
+ ],
1096
+ )
1097
+
1098
+
1099
+ class SparkConnectionBuilder(ConnectionBuilder):
1100
+ """Connection builder for Apache Spark."""
1101
+
1102
+ source_type = SourceType.SPARK
1103
+
1104
+ def build(self, config: dict[str, Any]) -> str:
1105
+ """Build Spark connection string."""
1106
+ connection_type = config.get("connection_type", "hive")
1107
+ host = config.get("host", "localhost")
1108
+ port = config.get("port", 10000)
1109
+ database = config.get("database", "default")
1110
+ username = config.get("username", "")
1111
+ password = quote_plus(config.get("password", ""))
1112
+
1113
+ if connection_type == "hive":
1114
+ # Hive JDBC connection
1115
+ if username:
1116
+ return f"hive://{username}:{password}@{host}:{port}/{database}"
1117
+ return f"hive://{host}:{port}/{database}"
1118
+ elif connection_type == "spark_thrift":
1119
+ # Spark Thrift Server
1120
+ return f"hive://{host}:{port}/{database}"
1121
+ else:
1122
+ # Generic Spark connection
1123
+ return f"spark://{host}:{port}/{database}"
1124
+
1125
+ def validate_config(self, config: dict[str, Any]) -> list[str]:
1126
+ """Validate Spark configuration."""
1127
+ errors = []
1128
+ if not config.get("host"):
1129
+ errors.append("host is required")
1130
+ return errors
1131
+
1132
+ @classmethod
1133
+ def get_definition(cls) -> SourceTypeDefinition:
1134
+ """Get the source type definition for UI rendering."""
1135
+ return SourceTypeDefinition(
1136
+ type=SourceType.SPARK.value,
1137
+ name="Apache Spark",
1138
+ description="Apache Spark (via Hive/JDBC)",
1139
+ icon="zap",
1140
+ category="bigdata",
1141
+ docs_url="https://spark.apache.org/docs/latest/",
1142
+ fields=[
1143
+ FieldDefinition(
1144
+ name="connection_type",
1145
+ label="Connection Type",
1146
+ type=FieldType.SELECT,
1147
+ options=[
1148
+ {"value": "hive", "label": "Hive Metastore"},
1149
+ {"value": "spark_thrift", "label": "Spark Thrift Server"},
1150
+ ],
1151
+ default="hive",
1152
+ description="How to connect to Spark",
1153
+ ),
1154
+ FieldDefinition(
1155
+ name="host",
1156
+ label="Host",
1157
+ required=True,
1158
+ placeholder="localhost",
1159
+ description="Spark/Hive server hostname",
1160
+ ),
1161
+ FieldDefinition(
1162
+ name="port",
1163
+ label="Port",
1164
+ type=FieldType.NUMBER,
1165
+ default=10000,
1166
+ min_value=1,
1167
+ max_value=65535,
1168
+ description="Hive/Thrift server port",
1169
+ ),
1170
+ FieldDefinition(
1171
+ name="database",
1172
+ label="Database",
1173
+ placeholder="default",
1174
+ default="default",
1175
+ description="Hive database name",
1176
+ ),
1177
+ FieldDefinition(
1178
+ name="username",
1179
+ label="Username",
1180
+ description="Username (if authentication enabled)",
1181
+ ),
1182
+ FieldDefinition(
1183
+ name="password",
1184
+ label="Password",
1185
+ type=FieldType.PASSWORD,
1186
+ description="Password (if authentication enabled)",
1187
+ ),
1188
+ FieldDefinition(
1189
+ name="table",
1190
+ label="Table",
1191
+ placeholder="my_table",
1192
+ description="Table name to validate",
1193
+ ),
1194
+ ],
1195
+ )
1196
+
187
1197
 
188
1198
  # Registry of connection builders
189
1199
  CONNECTION_BUILDERS: dict[str, type[ConnectionBuilder]] = {
190
- "file": FileConnectionBuilder,
191
- "postgresql": PostgreSQLConnectionBuilder,
192
- "mysql": MySQLConnectionBuilder,
193
- "snowflake": SnowflakeConnectionBuilder,
194
- "bigquery": BigQueryConnectionBuilder,
1200
+ SourceType.FILE.value: FileConnectionBuilder,
1201
+ SourceType.POSTGRESQL.value: PostgreSQLConnectionBuilder,
1202
+ SourceType.MYSQL.value: MySQLConnectionBuilder,
1203
+ SourceType.SQLITE.value: SQLiteConnectionBuilder,
1204
+ SourceType.SNOWFLAKE.value: SnowflakeConnectionBuilder,
1205
+ SourceType.BIGQUERY.value: BigQueryConnectionBuilder,
1206
+ SourceType.REDSHIFT.value: RedshiftConnectionBuilder,
1207
+ SourceType.DATABRICKS.value: DatabricksConnectionBuilder,
1208
+ SourceType.ORACLE.value: OracleConnectionBuilder,
1209
+ SourceType.SQLSERVER.value: SQLServerConnectionBuilder,
1210
+ SourceType.SPARK.value: SparkConnectionBuilder,
195
1211
  }
196
1212
 
197
1213
 
@@ -289,43 +1305,54 @@ async def test_connection(source_type: str, config: dict[str, Any]) -> dict[str,
289
1305
  def get_supported_source_types() -> list[dict[str, Any]]:
290
1306
  """Get list of supported source types with their required fields.
291
1307
 
1308
+ Returns comprehensive information about each source type including
1309
+ field definitions for dynamic form rendering.
1310
+
292
1311
  Returns:
293
- List of source type descriptions.
1312
+ List of source type definitions.
1313
+ """
1314
+ result = []
1315
+ for source_type in SourceType:
1316
+ builder_class = CONNECTION_BUILDERS.get(source_type.value)
1317
+ if builder_class:
1318
+ definition = builder_class.get_definition()
1319
+ result.append(definition.to_dict())
1320
+ return result
1321
+
1322
+
1323
+ def get_source_type_categories() -> list[dict[str, str]]:
1324
+ """Get list of source type categories.
1325
+
1326
+ Returns:
1327
+ List of category definitions with name and description.
294
1328
  """
295
1329
  return [
296
- {
297
- "type": "file",
298
- "name": "File",
299
- "description": "CSV, Parquet, JSON, Excel files",
300
- "required_fields": ["path"],
301
- "optional_fields": [],
302
- },
303
- {
304
- "type": "postgresql",
305
- "name": "PostgreSQL",
306
- "description": "PostgreSQL database",
307
- "required_fields": ["host", "database", "username"],
308
- "optional_fields": ["port", "password", "schema"],
309
- },
310
- {
311
- "type": "mysql",
312
- "name": "MySQL",
313
- "description": "MySQL database",
314
- "required_fields": ["host", "database", "username"],
315
- "optional_fields": ["port", "password"],
316
- },
317
- {
318
- "type": "snowflake",
319
- "name": "Snowflake",
320
- "description": "Snowflake data warehouse",
321
- "required_fields": ["account", "database", "warehouse", "username"],
322
- "optional_fields": ["password", "schema", "role"],
323
- },
324
- {
325
- "type": "bigquery",
326
- "name": "BigQuery",
327
- "description": "Google BigQuery",
328
- "required_fields": ["project"],
329
- "optional_fields": ["dataset"],
330
- },
1330
+ {"value": "file", "label": "Files", "description": "Local file sources"},
1331
+ {"value": "database", "label": "Databases", "description": "Relational databases"},
1332
+ {"value": "warehouse", "label": "Data Warehouses", "description": "Cloud data warehouses"},
1333
+ {"value": "bigdata", "label": "Big Data", "description": "Big data platforms"},
331
1334
  ]
1335
+
1336
+
1337
+ def get_source_types_by_category() -> dict[str, list[dict[str, Any]]]:
1338
+ """Get source types grouped by category.
1339
+
1340
+ Returns:
1341
+ Dictionary mapping category to list of source type definitions.
1342
+ """
1343
+ categories: dict[str, list[dict[str, Any]]] = {
1344
+ "file": [],
1345
+ "database": [],
1346
+ "warehouse": [],
1347
+ "bigdata": [],
1348
+ }
1349
+
1350
+ for source_type in SourceType:
1351
+ builder_class = CONNECTION_BUILDERS.get(source_type.value)
1352
+ if builder_class:
1353
+ definition = builder_class.get_definition()
1354
+ category = definition.category
1355
+ if category in categories:
1356
+ categories[category].append(definition.to_dict())
1357
+
1358
+ return categories