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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
- truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
- truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
- truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|