dvt-core 0.58.6__cp311-cp311-macosx_10_9_x86_64.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 (324) hide show
  1. dbt/__init__.py +7 -0
  2. dbt/_pydantic_shim.py +26 -0
  3. dbt/artifacts/__init__.py +0 -0
  4. dbt/artifacts/exceptions/__init__.py +1 -0
  5. dbt/artifacts/exceptions/schemas.py +31 -0
  6. dbt/artifacts/resources/__init__.py +116 -0
  7. dbt/artifacts/resources/base.py +67 -0
  8. dbt/artifacts/resources/types.py +93 -0
  9. dbt/artifacts/resources/v1/analysis.py +10 -0
  10. dbt/artifacts/resources/v1/catalog.py +23 -0
  11. dbt/artifacts/resources/v1/components.py +274 -0
  12. dbt/artifacts/resources/v1/config.py +277 -0
  13. dbt/artifacts/resources/v1/documentation.py +11 -0
  14. dbt/artifacts/resources/v1/exposure.py +51 -0
  15. dbt/artifacts/resources/v1/function.py +52 -0
  16. dbt/artifacts/resources/v1/generic_test.py +31 -0
  17. dbt/artifacts/resources/v1/group.py +21 -0
  18. dbt/artifacts/resources/v1/hook.py +11 -0
  19. dbt/artifacts/resources/v1/macro.py +29 -0
  20. dbt/artifacts/resources/v1/metric.py +172 -0
  21. dbt/artifacts/resources/v1/model.py +145 -0
  22. dbt/artifacts/resources/v1/owner.py +10 -0
  23. dbt/artifacts/resources/v1/saved_query.py +111 -0
  24. dbt/artifacts/resources/v1/seed.py +41 -0
  25. dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  26. dbt/artifacts/resources/v1/semantic_model.py +314 -0
  27. dbt/artifacts/resources/v1/singular_test.py +14 -0
  28. dbt/artifacts/resources/v1/snapshot.py +91 -0
  29. dbt/artifacts/resources/v1/source_definition.py +84 -0
  30. dbt/artifacts/resources/v1/sql_operation.py +10 -0
  31. dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
  32. dbt/artifacts/schemas/__init__.py +0 -0
  33. dbt/artifacts/schemas/base.py +191 -0
  34. dbt/artifacts/schemas/batch_results.py +24 -0
  35. dbt/artifacts/schemas/catalog/__init__.py +11 -0
  36. dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  37. dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
  38. dbt/artifacts/schemas/freshness/__init__.py +1 -0
  39. dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  40. dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
  41. dbt/artifacts/schemas/manifest/__init__.py +2 -0
  42. dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  43. dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
  44. dbt/artifacts/schemas/results.py +147 -0
  45. dbt/artifacts/schemas/run/__init__.py +2 -0
  46. dbt/artifacts/schemas/run/v5/__init__.py +0 -0
  47. dbt/artifacts/schemas/run/v5/run.py +184 -0
  48. dbt/artifacts/schemas/upgrades/__init__.py +4 -0
  49. dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  50. dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  51. dbt/artifacts/utils/validation.py +153 -0
  52. dbt/cli/__init__.py +1 -0
  53. dbt/cli/context.py +17 -0
  54. dbt/cli/exceptions.py +57 -0
  55. dbt/cli/flags.py +560 -0
  56. dbt/cli/main.py +2403 -0
  57. dbt/cli/option_types.py +121 -0
  58. dbt/cli/options.py +80 -0
  59. dbt/cli/params.py +844 -0
  60. dbt/cli/requires.py +490 -0
  61. dbt/cli/resolvers.py +50 -0
  62. dbt/cli/types.py +40 -0
  63. dbt/clients/__init__.py +0 -0
  64. dbt/clients/checked_load.py +83 -0
  65. dbt/clients/git.py +164 -0
  66. dbt/clients/jinja.py +206 -0
  67. dbt/clients/jinja_static.py +245 -0
  68. dbt/clients/registry.py +192 -0
  69. dbt/clients/yaml_helper.py +68 -0
  70. dbt/compilation.py +876 -0
  71. dbt/compute/__init__.py +14 -0
  72. dbt/compute/engines/__init__.py +12 -0
  73. dbt/compute/engines/spark_engine.cpython-311-darwin.so +0 -0
  74. dbt/compute/engines/spark_engine.py +642 -0
  75. dbt/compute/federated_executor.cpython-311-darwin.so +0 -0
  76. dbt/compute/federated_executor.py +1080 -0
  77. dbt/compute/filter_pushdown.cpython-311-darwin.so +0 -0
  78. dbt/compute/filter_pushdown.py +273 -0
  79. dbt/compute/jar_provisioning.cpython-311-darwin.so +0 -0
  80. dbt/compute/jar_provisioning.py +255 -0
  81. dbt/compute/java_compat.cpython-311-darwin.so +0 -0
  82. dbt/compute/java_compat.py +689 -0
  83. dbt/compute/jdbc_utils.cpython-311-darwin.so +0 -0
  84. dbt/compute/jdbc_utils.py +678 -0
  85. dbt/compute/metadata/__init__.py +40 -0
  86. dbt/compute/metadata/adapters_registry.cpython-311-darwin.so +0 -0
  87. dbt/compute/metadata/adapters_registry.py +370 -0
  88. dbt/compute/metadata/registry.cpython-311-darwin.so +0 -0
  89. dbt/compute/metadata/registry.py +674 -0
  90. dbt/compute/metadata/store.cpython-311-darwin.so +0 -0
  91. dbt/compute/metadata/store.py +1499 -0
  92. dbt/compute/smart_selector.cpython-311-darwin.so +0 -0
  93. dbt/compute/smart_selector.py +377 -0
  94. dbt/compute/strategies/__init__.py +55 -0
  95. dbt/compute/strategies/base.cpython-311-darwin.so +0 -0
  96. dbt/compute/strategies/base.py +165 -0
  97. dbt/compute/strategies/dataproc.cpython-311-darwin.so +0 -0
  98. dbt/compute/strategies/dataproc.py +207 -0
  99. dbt/compute/strategies/emr.cpython-311-darwin.so +0 -0
  100. dbt/compute/strategies/emr.py +203 -0
  101. dbt/compute/strategies/local.cpython-311-darwin.so +0 -0
  102. dbt/compute/strategies/local.py +443 -0
  103. dbt/compute/strategies/standalone.cpython-311-darwin.so +0 -0
  104. dbt/compute/strategies/standalone.py +262 -0
  105. dbt/config/__init__.py +4 -0
  106. dbt/config/catalogs.py +94 -0
  107. dbt/config/compute.cpython-311-darwin.so +0 -0
  108. dbt/config/compute.py +513 -0
  109. dbt/config/dvt_profile.cpython-311-darwin.so +0 -0
  110. dbt/config/dvt_profile.py +342 -0
  111. dbt/config/profile.py +422 -0
  112. dbt/config/project.py +873 -0
  113. dbt/config/project_utils.py +28 -0
  114. dbt/config/renderer.py +231 -0
  115. dbt/config/runtime.py +553 -0
  116. dbt/config/selectors.py +208 -0
  117. dbt/config/utils.py +77 -0
  118. dbt/constants.py +28 -0
  119. dbt/context/__init__.py +0 -0
  120. dbt/context/base.py +745 -0
  121. dbt/context/configured.py +135 -0
  122. dbt/context/context_config.py +382 -0
  123. dbt/context/docs.py +82 -0
  124. dbt/context/exceptions_jinja.py +178 -0
  125. dbt/context/macro_resolver.py +195 -0
  126. dbt/context/macros.py +171 -0
  127. dbt/context/manifest.py +72 -0
  128. dbt/context/providers.py +2249 -0
  129. dbt/context/query_header.py +13 -0
  130. dbt/context/secret.py +58 -0
  131. dbt/context/target.py +74 -0
  132. dbt/contracts/__init__.py +0 -0
  133. dbt/contracts/files.py +413 -0
  134. dbt/contracts/graph/__init__.py +0 -0
  135. dbt/contracts/graph/manifest.py +1904 -0
  136. dbt/contracts/graph/metrics.py +97 -0
  137. dbt/contracts/graph/model_config.py +70 -0
  138. dbt/contracts/graph/node_args.py +42 -0
  139. dbt/contracts/graph/nodes.py +1806 -0
  140. dbt/contracts/graph/semantic_manifest.py +232 -0
  141. dbt/contracts/graph/unparsed.py +811 -0
  142. dbt/contracts/project.py +417 -0
  143. dbt/contracts/results.py +53 -0
  144. dbt/contracts/selection.py +23 -0
  145. dbt/contracts/sql.py +85 -0
  146. dbt/contracts/state.py +68 -0
  147. dbt/contracts/util.py +46 -0
  148. dbt/deprecations.py +348 -0
  149. dbt/deps/__init__.py +0 -0
  150. dbt/deps/base.py +152 -0
  151. dbt/deps/git.py +195 -0
  152. dbt/deps/local.py +79 -0
  153. dbt/deps/registry.py +130 -0
  154. dbt/deps/resolver.py +149 -0
  155. dbt/deps/tarball.py +120 -0
  156. dbt/docs/source/_ext/dbt_click.py +119 -0
  157. dbt/docs/source/conf.py +32 -0
  158. dbt/env_vars.py +64 -0
  159. dbt/event_time/event_time.py +40 -0
  160. dbt/event_time/sample_window.py +60 -0
  161. dbt/events/__init__.py +15 -0
  162. dbt/events/base_types.py +36 -0
  163. dbt/events/core_types_pb2.py +2 -0
  164. dbt/events/logging.py +108 -0
  165. dbt/events/types.py +2516 -0
  166. dbt/exceptions.py +1486 -0
  167. dbt/flags.py +89 -0
  168. dbt/graph/__init__.py +11 -0
  169. dbt/graph/cli.py +249 -0
  170. dbt/graph/graph.py +172 -0
  171. dbt/graph/queue.py +214 -0
  172. dbt/graph/selector.py +374 -0
  173. dbt/graph/selector_methods.py +975 -0
  174. dbt/graph/selector_spec.py +222 -0
  175. dbt/graph/thread_pool.py +18 -0
  176. dbt/hooks.py +21 -0
  177. dbt/include/README.md +49 -0
  178. dbt/include/__init__.py +3 -0
  179. dbt/include/data/adapters_registry.duckdb +0 -0
  180. dbt/include/data/build_registry.py +242 -0
  181. dbt/include/data/csv/adapter_queries.csv +33 -0
  182. dbt/include/data/csv/syntax_rules.csv +9 -0
  183. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  184. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  185. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  186. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  187. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  188. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  189. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  190. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  191. dbt/include/starter_project/.gitignore +4 -0
  192. dbt/include/starter_project/README.md +15 -0
  193. dbt/include/starter_project/__init__.py +3 -0
  194. dbt/include/starter_project/analyses/.gitkeep +0 -0
  195. dbt/include/starter_project/dbt_project.yml +36 -0
  196. dbt/include/starter_project/macros/.gitkeep +0 -0
  197. dbt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  198. dbt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  199. dbt/include/starter_project/models/example/schema.yml +21 -0
  200. dbt/include/starter_project/seeds/.gitkeep +0 -0
  201. dbt/include/starter_project/snapshots/.gitkeep +0 -0
  202. dbt/include/starter_project/tests/.gitkeep +0 -0
  203. dbt/internal_deprecations.py +26 -0
  204. dbt/jsonschemas/__init__.py +3 -0
  205. dbt/jsonschemas/jsonschemas.py +309 -0
  206. dbt/jsonschemas/project/0.0.110.json +4717 -0
  207. dbt/jsonschemas/project/0.0.85.json +2015 -0
  208. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  209. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  210. dbt/jsonschemas/resources/latest.json +6773 -0
  211. dbt/links.py +4 -0
  212. dbt/materializations/__init__.py +0 -0
  213. dbt/materializations/incremental/__init__.py +0 -0
  214. dbt/materializations/incremental/microbatch.py +236 -0
  215. dbt/mp_context.py +8 -0
  216. dbt/node_types.py +37 -0
  217. dbt/parser/__init__.py +23 -0
  218. dbt/parser/analysis.py +21 -0
  219. dbt/parser/base.py +548 -0
  220. dbt/parser/common.py +266 -0
  221. dbt/parser/docs.py +52 -0
  222. dbt/parser/fixtures.py +51 -0
  223. dbt/parser/functions.py +30 -0
  224. dbt/parser/generic_test.py +100 -0
  225. dbt/parser/generic_test_builders.py +333 -0
  226. dbt/parser/hooks.py +118 -0
  227. dbt/parser/macros.py +137 -0
  228. dbt/parser/manifest.py +2204 -0
  229. dbt/parser/models.py +573 -0
  230. dbt/parser/partial.py +1178 -0
  231. dbt/parser/read_files.py +445 -0
  232. dbt/parser/schema_generic_tests.py +422 -0
  233. dbt/parser/schema_renderer.py +111 -0
  234. dbt/parser/schema_yaml_readers.py +935 -0
  235. dbt/parser/schemas.py +1466 -0
  236. dbt/parser/search.py +149 -0
  237. dbt/parser/seeds.py +28 -0
  238. dbt/parser/singular_test.py +20 -0
  239. dbt/parser/snapshots.py +44 -0
  240. dbt/parser/sources.py +558 -0
  241. dbt/parser/sql.py +62 -0
  242. dbt/parser/unit_tests.py +621 -0
  243. dbt/plugins/__init__.py +20 -0
  244. dbt/plugins/contracts.py +9 -0
  245. dbt/plugins/exceptions.py +2 -0
  246. dbt/plugins/manager.py +163 -0
  247. dbt/plugins/manifest.py +21 -0
  248. dbt/profiler.py +20 -0
  249. dbt/py.typed +1 -0
  250. dbt/query_analyzer.cpython-311-darwin.so +0 -0
  251. dbt/query_analyzer.py +410 -0
  252. dbt/runners/__init__.py +2 -0
  253. dbt/runners/exposure_runner.py +7 -0
  254. dbt/runners/no_op_runner.py +45 -0
  255. dbt/runners/saved_query_runner.py +7 -0
  256. dbt/selected_resources.py +8 -0
  257. dbt/task/__init__.py +0 -0
  258. dbt/task/base.py +503 -0
  259. dbt/task/build.py +197 -0
  260. dbt/task/clean.py +56 -0
  261. dbt/task/clone.py +161 -0
  262. dbt/task/compile.py +150 -0
  263. dbt/task/compute.cpython-311-darwin.so +0 -0
  264. dbt/task/compute.py +458 -0
  265. dbt/task/debug.py +505 -0
  266. dbt/task/deps.py +280 -0
  267. dbt/task/docs/__init__.py +3 -0
  268. dbt/task/docs/api/__init__.py +23 -0
  269. dbt/task/docs/api/catalog.cpython-311-darwin.so +0 -0
  270. dbt/task/docs/api/catalog.py +204 -0
  271. dbt/task/docs/api/lineage.cpython-311-darwin.so +0 -0
  272. dbt/task/docs/api/lineage.py +234 -0
  273. dbt/task/docs/api/profile.cpython-311-darwin.so +0 -0
  274. dbt/task/docs/api/profile.py +204 -0
  275. dbt/task/docs/api/spark.cpython-311-darwin.so +0 -0
  276. dbt/task/docs/api/spark.py +186 -0
  277. dbt/task/docs/generate.py +947 -0
  278. dbt/task/docs/index.html +250 -0
  279. dbt/task/docs/serve.cpython-311-darwin.so +0 -0
  280. dbt/task/docs/serve.py +174 -0
  281. dbt/task/dvt_output.py +362 -0
  282. dbt/task/dvt_run.py +204 -0
  283. dbt/task/freshness.py +322 -0
  284. dbt/task/function.py +121 -0
  285. dbt/task/group_lookup.py +46 -0
  286. dbt/task/init.cpython-311-darwin.so +0 -0
  287. dbt/task/init.py +604 -0
  288. dbt/task/java.cpython-311-darwin.so +0 -0
  289. dbt/task/java.py +316 -0
  290. dbt/task/list.py +236 -0
  291. dbt/task/metadata.cpython-311-darwin.so +0 -0
  292. dbt/task/metadata.py +804 -0
  293. dbt/task/printer.py +175 -0
  294. dbt/task/profile.cpython-311-darwin.so +0 -0
  295. dbt/task/profile.py +1307 -0
  296. dbt/task/profile_serve.py +615 -0
  297. dbt/task/retract.py +438 -0
  298. dbt/task/retry.py +175 -0
  299. dbt/task/run.py +1387 -0
  300. dbt/task/run_operation.py +141 -0
  301. dbt/task/runnable.py +758 -0
  302. dbt/task/seed.py +103 -0
  303. dbt/task/show.py +149 -0
  304. dbt/task/snapshot.py +56 -0
  305. dbt/task/spark.cpython-311-darwin.so +0 -0
  306. dbt/task/spark.py +414 -0
  307. dbt/task/sql.py +110 -0
  308. dbt/task/target_sync.cpython-311-darwin.so +0 -0
  309. dbt/task/target_sync.py +766 -0
  310. dbt/task/test.py +464 -0
  311. dbt/tests/fixtures/__init__.py +1 -0
  312. dbt/tests/fixtures/project.py +620 -0
  313. dbt/tests/util.py +651 -0
  314. dbt/tracking.py +529 -0
  315. dbt/utils/__init__.py +3 -0
  316. dbt/utils/artifact_upload.py +151 -0
  317. dbt/utils/utils.py +408 -0
  318. dbt/version.py +270 -0
  319. dvt_cli/__init__.py +72 -0
  320. dvt_core-0.58.6.dist-info/METADATA +288 -0
  321. dvt_core-0.58.6.dist-info/RECORD +324 -0
  322. dvt_core-0.58.6.dist-info/WHEEL +5 -0
  323. dvt_core-0.58.6.dist-info/entry_points.txt +2 -0
  324. dvt_core-0.58.6.dist-info/top_level.txt +2 -0
@@ -0,0 +1,615 @@
1
+ # =============================================================================
2
+ # DVT Profile Serve - Web UI for Profiling Results
3
+ # =============================================================================
4
+ # Serves a beautiful web interface to view profiling results stored in
5
+ # metadata_store.duckdb, similar to PipeRider's report viewer.
6
+ #
7
+ # Usage:
8
+ # dvt profile serve # Start server on http://localhost:8580
9
+ # dvt profile serve --port 9000 # Custom port
10
+ # dvt profile serve --no-browser # Don't auto-open browser
11
+ #
12
+ # Installation:
13
+ # Copy this file to: core/dbt/task/profile_serve.py
14
+ #
15
+ # DVT v0.58.0: New web UI for profiling results
16
+ # =============================================================================
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import threading
22
+ import webbrowser
23
+ from datetime import datetime
24
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional
27
+ from urllib.parse import parse_qs, urlparse
28
+
29
+ # Try to import Rich for CLI output
30
+ try:
31
+ from rich.console import Console
32
+ from rich.panel import Panel
33
+ from rich import box
34
+ console = Console()
35
+ HAS_RICH = True
36
+ except ImportError:
37
+ HAS_RICH = False
38
+ console = None
39
+
40
+
41
+ class ProfileAPIHandler(SimpleHTTPRequestHandler):
42
+ """HTTP handler for the Profile Viewer API and static files."""
43
+
44
+ def __init__(self, *args, metadata_store_path: Path = None, **kwargs):
45
+ self.metadata_store_path = metadata_store_path
46
+ super().__init__(*args, **kwargs)
47
+
48
+ def do_GET(self):
49
+ """Handle GET requests."""
50
+ parsed = urlparse(self.path)
51
+ path = parsed.path
52
+
53
+ # API endpoints
54
+ if path == "/api/profiles":
55
+ self._serve_profiles_list()
56
+ elif path == "/api/profile":
57
+ query = parse_qs(parsed.query)
58
+ table_name = query.get("table", [None])[0]
59
+ self._serve_profile_detail(table_name)
60
+ elif path == "/api/summary":
61
+ self._serve_summary()
62
+ elif path == "/" or path == "/index.html":
63
+ self._serve_html()
64
+ else:
65
+ # Serve static files
66
+ super().do_GET()
67
+
68
+ def _serve_json(self, data: Any, status: int = 200):
69
+ """Send JSON response."""
70
+ self.send_response(status)
71
+ self.send_header("Content-Type", "application/json")
72
+ self.send_header("Access-Control-Allow-Origin", "*")
73
+ self.end_headers()
74
+ self.wfile.write(json.dumps(data, default=str).encode())
75
+
76
+ def _serve_html(self):
77
+ """Serve the main HTML page."""
78
+ html = self._generate_html()
79
+ self.send_response(200)
80
+ self.send_header("Content-Type", "text/html")
81
+ self.end_headers()
82
+ self.wfile.write(html.encode())
83
+
84
+ def _get_connection(self):
85
+ """Get DuckDB connection to metadata store."""
86
+ try:
87
+ import duckdb
88
+ return duckdb.connect(str(self.metadata_store_path), read_only=True)
89
+ except Exception as e:
90
+ return None
91
+
92
+ def _serve_profiles_list(self):
93
+ """Serve list of all profiled tables."""
94
+ conn = self._get_connection()
95
+ if not conn:
96
+ self._serve_json({"error": "Could not connect to metadata store"}, 500)
97
+ return
98
+
99
+ try:
100
+ # Query profile results
101
+ result = conn.execute("""
102
+ SELECT DISTINCT
103
+ source_name,
104
+ table_name,
105
+ adapter_name,
106
+ connection_name,
107
+ COUNT(*) as column_count,
108
+ MAX(last_refreshed) as last_profiled
109
+ FROM column_metadata
110
+ GROUP BY source_name, table_name, adapter_name, connection_name
111
+ ORDER BY source_name, table_name
112
+ """).fetchall()
113
+
114
+ profiles = []
115
+ for row in result:
116
+ profiles.append({
117
+ "source_name": row[0],
118
+ "table_name": row[1],
119
+ "adapter_name": row[2],
120
+ "connection_name": row[3],
121
+ "column_count": row[4],
122
+ "last_profiled": row[5],
123
+ })
124
+
125
+ self._serve_json({"profiles": profiles})
126
+ except Exception as e:
127
+ self._serve_json({"profiles": [], "error": str(e)})
128
+ finally:
129
+ conn.close()
130
+
131
+ def _serve_profile_detail(self, table_name: str):
132
+ """Serve detailed profile for a specific table."""
133
+ if not table_name:
134
+ self._serve_json({"error": "table parameter required"}, 400)
135
+ return
136
+
137
+ conn = self._get_connection()
138
+ if not conn:
139
+ self._serve_json({"error": "Could not connect to metadata store"}, 500)
140
+ return
141
+
142
+ try:
143
+ # Query column metadata
144
+ result = conn.execute("""
145
+ SELECT
146
+ column_name,
147
+ adapter_type,
148
+ spark_type,
149
+ is_nullable,
150
+ is_primary_key,
151
+ ordinal_position,
152
+ last_refreshed
153
+ FROM column_metadata
154
+ WHERE table_name = ?
155
+ ORDER BY ordinal_position
156
+ """, [table_name]).fetchall()
157
+
158
+ columns = []
159
+ for row in result:
160
+ columns.append({
161
+ "name": row[0],
162
+ "adapter_type": row[1],
163
+ "spark_type": row[2],
164
+ "is_nullable": row[3],
165
+ "is_primary_key": row[4],
166
+ "ordinal_position": row[5],
167
+ "last_refreshed": row[6],
168
+ })
169
+
170
+ # Try to get row count if available
171
+ row_count = None
172
+ try:
173
+ count_result = conn.execute("""
174
+ SELECT row_count FROM row_counts WHERE table_name = ?
175
+ """, [table_name]).fetchone()
176
+ if count_result:
177
+ row_count = count_result[0]
178
+ except:
179
+ pass
180
+
181
+ self._serve_json({
182
+ "table_name": table_name,
183
+ "columns": columns,
184
+ "row_count": row_count,
185
+ })
186
+ except Exception as e:
187
+ self._serve_json({"error": str(e)}, 500)
188
+ finally:
189
+ conn.close()
190
+
191
+ def _serve_summary(self):
192
+ """Serve summary statistics."""
193
+ conn = self._get_connection()
194
+ if not conn:
195
+ self._serve_json({"error": "Could not connect to metadata store"}, 500)
196
+ return
197
+
198
+ try:
199
+ # Get summary stats
200
+ tables = conn.execute("""
201
+ SELECT COUNT(DISTINCT table_name) FROM column_metadata
202
+ """).fetchone()[0]
203
+
204
+ columns = conn.execute("""
205
+ SELECT COUNT(*) FROM column_metadata
206
+ """).fetchone()[0]
207
+
208
+ sources = conn.execute("""
209
+ SELECT COUNT(DISTINCT source_name) FROM column_metadata
210
+ """).fetchone()[0]
211
+
212
+ # Get unique connections/adapters
213
+ adapters = conn.execute("""
214
+ SELECT COUNT(DISTINCT adapter_name) FROM column_metadata
215
+ """).fetchone()[0]
216
+
217
+ self._serve_json({
218
+ "total_tables": tables,
219
+ "total_columns": columns,
220
+ "sources": sources,
221
+ "adapters": adapters,
222
+ })
223
+ except Exception as e:
224
+ self._serve_json({"error": str(e)}, 500)
225
+ finally:
226
+ conn.close()
227
+
228
+ def _generate_html(self) -> str:
229
+ """Generate the HTML page for the profile viewer."""
230
+ return '''<!DOCTYPE html>
231
+ <html lang="en">
232
+ <head>
233
+ <meta charset="UTF-8">
234
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
235
+ <title>DVT Profile Viewer</title>
236
+ <style>
237
+ :root {
238
+ --primary: #6366f1;
239
+ --primary-dark: #4f46e5;
240
+ --success: #10b981;
241
+ --warning: #f59e0b;
242
+ --error: #ef4444;
243
+ --bg: #0f172a;
244
+ --bg-card: #1e293b;
245
+ --text: #f1f5f9;
246
+ --text-dim: #94a3b8;
247
+ --border: #334155;
248
+ }
249
+ * { box-sizing: border-box; margin: 0; padding: 0; }
250
+ body {
251
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
252
+ background: var(--bg);
253
+ color: var(--text);
254
+ min-height: 100vh;
255
+ }
256
+ .header {
257
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
258
+ padding: 2rem;
259
+ text-align: center;
260
+ }
261
+ .header h1 { font-size: 2rem; margin-bottom: 0.5rem; }
262
+ .header p { color: rgba(255,255,255,0.8); }
263
+ .container { max-width: 1400px; margin: 0 auto; padding: 2rem; }
264
+ .stats-grid {
265
+ display: grid;
266
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
267
+ gap: 1rem;
268
+ margin-bottom: 2rem;
269
+ }
270
+ .stat-card {
271
+ background: var(--bg-card);
272
+ border-radius: 12px;
273
+ padding: 1.5rem;
274
+ border: 1px solid var(--border);
275
+ }
276
+ .stat-card h3 { color: var(--text-dim); font-size: 0.875rem; margin-bottom: 0.5rem; }
277
+ .stat-card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
278
+ .tables-section { margin-top: 2rem; }
279
+ .tables-section h2 { margin-bottom: 1rem; }
280
+ .table-list {
281
+ display: grid;
282
+ gap: 1rem;
283
+ }
284
+ .table-card {
285
+ background: var(--bg-card);
286
+ border-radius: 12px;
287
+ padding: 1.5rem;
288
+ border: 1px solid var(--border);
289
+ cursor: pointer;
290
+ transition: all 0.2s;
291
+ }
292
+ .table-card:hover {
293
+ border-color: var(--primary);
294
+ transform: translateY(-2px);
295
+ }
296
+ .table-card.selected {
297
+ border-color: var(--primary);
298
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
299
+ }
300
+ .table-header {
301
+ display: flex;
302
+ justify-content: space-between;
303
+ align-items: center;
304
+ margin-bottom: 0.5rem;
305
+ }
306
+ .table-name { font-weight: 600; font-size: 1.1rem; }
307
+ .table-type {
308
+ font-size: 0.75rem;
309
+ padding: 0.25rem 0.5rem;
310
+ border-radius: 4px;
311
+ background: var(--primary);
312
+ }
313
+ .table-type.source { background: var(--success); }
314
+ .table-type.model { background: var(--warning); }
315
+ .table-meta { color: var(--text-dim); font-size: 0.875rem; }
316
+ .detail-panel {
317
+ position: fixed;
318
+ top: 0;
319
+ right: -500px;
320
+ width: 500px;
321
+ height: 100vh;
322
+ background: var(--bg-card);
323
+ border-left: 1px solid var(--border);
324
+ transition: right 0.3s;
325
+ overflow-y: auto;
326
+ z-index: 100;
327
+ }
328
+ .detail-panel.open { right: 0; }
329
+ .detail-header {
330
+ padding: 1.5rem;
331
+ border-bottom: 1px solid var(--border);
332
+ display: flex;
333
+ justify-content: space-between;
334
+ align-items: center;
335
+ }
336
+ .detail-header h2 { font-size: 1.25rem; }
337
+ .close-btn {
338
+ background: none;
339
+ border: none;
340
+ color: var(--text);
341
+ font-size: 1.5rem;
342
+ cursor: pointer;
343
+ }
344
+ .detail-content { padding: 1.5rem; }
345
+ .column-table {
346
+ width: 100%;
347
+ border-collapse: collapse;
348
+ }
349
+ .column-table th, .column-table td {
350
+ padding: 0.75rem;
351
+ text-align: left;
352
+ border-bottom: 1px solid var(--border);
353
+ }
354
+ .column-table th {
355
+ color: var(--text-dim);
356
+ font-weight: 500;
357
+ font-size: 0.75rem;
358
+ text-transform: uppercase;
359
+ }
360
+ .type-badge {
361
+ font-family: monospace;
362
+ font-size: 0.8rem;
363
+ background: rgba(99, 102, 241, 0.2);
364
+ padding: 0.25rem 0.5rem;
365
+ border-radius: 4px;
366
+ }
367
+ .loading {
368
+ text-align: center;
369
+ padding: 3rem;
370
+ color: var(--text-dim);
371
+ }
372
+ .error {
373
+ background: rgba(239, 68, 68, 0.2);
374
+ border: 1px solid var(--error);
375
+ padding: 1rem;
376
+ border-radius: 8px;
377
+ margin: 1rem 0;
378
+ }
379
+ @media (max-width: 768px) {
380
+ .detail-panel { width: 100%; right: -100%; }
381
+ }
382
+ </style>
383
+ </head>
384
+ <body>
385
+ <div class="header">
386
+ <h1>🔍 DVT Profile Viewer</h1>
387
+ <p>Data profiling results from metadata_store.duckdb</p>
388
+ </div>
389
+
390
+ <div class="container">
391
+ <div class="stats-grid" id="stats">
392
+ <div class="stat-card">
393
+ <h3>Total Tables</h3>
394
+ <div class="value" id="stat-tables">-</div>
395
+ </div>
396
+ <div class="stat-card">
397
+ <h3>Total Columns</h3>
398
+ <div class="value" id="stat-columns">-</div>
399
+ </div>
400
+ <div class="stat-card">
401
+ <h3>Sources</h3>
402
+ <div class="value" id="stat-sources">-</div>
403
+ </div>
404
+ <div class="stat-card">
405
+ <h3>Models</h3>
406
+ <div class="value" id="stat-models">-</div>
407
+ </div>
408
+ </div>
409
+
410
+ <div class="tables-section">
411
+ <h2>Profiled Tables</h2>
412
+ <div class="table-list" id="table-list">
413
+ <div class="loading">Loading profiles...</div>
414
+ </div>
415
+ </div>
416
+ </div>
417
+
418
+ <div class="detail-panel" id="detail-panel">
419
+ <div class="detail-header">
420
+ <h2 id="detail-title">Table Details</h2>
421
+ <button class="close-btn" onclick="closeDetail()">&times;</button>
422
+ </div>
423
+ <div class="detail-content" id="detail-content">
424
+ <div class="loading">Select a table to view details</div>
425
+ </div>
426
+ </div>
427
+
428
+ <script>
429
+ async function loadSummary() {
430
+ try {
431
+ const resp = await fetch('/api/summary');
432
+ const data = await resp.json();
433
+ document.getElementById('stat-tables').textContent = data.total_tables || 0;
434
+ document.getElementById('stat-columns').textContent = data.total_columns || 0;
435
+ document.getElementById('stat-sources').textContent = data.sources || 0;
436
+ document.getElementById('stat-models').textContent = data.models || 0;
437
+ } catch (e) {
438
+ console.error('Failed to load summary:', e);
439
+ }
440
+ }
441
+
442
+ async function loadProfiles() {
443
+ const container = document.getElementById('table-list');
444
+ try {
445
+ const resp = await fetch('/api/profiles');
446
+ const data = await resp.json();
447
+
448
+ if (data.profiles.length === 0) {
449
+ container.innerHTML = '<div class="loading">No profiles found. Run "dvt profile" first.</div>';
450
+ return;
451
+ }
452
+
453
+ container.innerHTML = data.profiles.map(p => `
454
+ <div class="table-card" onclick="showDetail('${p.table_name}')">
455
+ <div class="table-header">
456
+ <span class="table-name">${p.source_name ? p.source_name + '.' : ''}${p.table_name}</span>
457
+ <span class="table-type ${p.type}">${p.type}</span>
458
+ </div>
459
+ <div class="table-meta">
460
+ ${p.column_count} columns • Last profiled: ${new Date(p.last_profiled).toLocaleString()}
461
+ </div>
462
+ </div>
463
+ `).join('');
464
+ } catch (e) {
465
+ container.innerHTML = `<div class="error">Failed to load profiles: ${e.message}</div>`;
466
+ }
467
+ }
468
+
469
+ async function showDetail(tableName) {
470
+ const panel = document.getElementById('detail-panel');
471
+ const title = document.getElementById('detail-title');
472
+ const content = document.getElementById('detail-content');
473
+
474
+ panel.classList.add('open');
475
+ title.textContent = tableName;
476
+ content.innerHTML = '<div class="loading">Loading...</div>';
477
+
478
+ // Highlight selected
479
+ document.querySelectorAll('.table-card').forEach(c => c.classList.remove('selected'));
480
+ event.currentTarget.classList.add('selected');
481
+
482
+ try {
483
+ const resp = await fetch(`/api/profile?table=${encodeURIComponent(tableName)}`);
484
+ const data = await resp.json();
485
+
486
+ if (data.error) {
487
+ content.innerHTML = `<div class="error">${data.error}</div>`;
488
+ return;
489
+ }
490
+
491
+ let html = '';
492
+ if (data.row_count !== null) {
493
+ html += `<p style="margin-bottom: 1rem;">Row count: <strong>${data.row_count.toLocaleString()}</strong></p>`;
494
+ }
495
+
496
+ html += `
497
+ <table class="column-table">
498
+ <thead>
499
+ <tr>
500
+ <th>#</th>
501
+ <th>Column</th>
502
+ <th>Type</th>
503
+ <th>Spark Type</th>
504
+ </tr>
505
+ </thead>
506
+ <tbody>
507
+ ${data.columns.map(c => `
508
+ <tr>
509
+ <td>${c.ordinal_position}</td>
510
+ <td><strong>${c.name}</strong></td>
511
+ <td><span class="type-badge">${c.adapter_type || '-'}</span></td>
512
+ <td><span class="type-badge">${c.spark_type || '-'}</span></td>
513
+ </tr>
514
+ `).join('')}
515
+ </tbody>
516
+ </table>
517
+ `;
518
+
519
+ content.innerHTML = html;
520
+ } catch (e) {
521
+ content.innerHTML = `<div class="error">Failed to load details: ${e.message}</div>`;
522
+ }
523
+ }
524
+
525
+ function closeDetail() {
526
+ document.getElementById('detail-panel').classList.remove('open');
527
+ document.querySelectorAll('.table-card').forEach(c => c.classList.remove('selected'));
528
+ }
529
+
530
+ // Initialize
531
+ loadSummary();
532
+ loadProfiles();
533
+ </script>
534
+ </body>
535
+ </html>'''
536
+
537
+ def log_message(self, format, *args):
538
+ """Suppress default logging."""
539
+ pass
540
+
541
+
542
+ def serve_profile_ui(
543
+ project_dir: Path,
544
+ port: int = 8580,
545
+ host: str = "localhost",
546
+ open_browser: bool = True,
547
+ ):
548
+ """
549
+ Start the profile viewer web server.
550
+
551
+ Args:
552
+ project_dir: Path to the DVT project
553
+ port: Port to serve on (default: 8580)
554
+ host: Host to bind to (default: localhost)
555
+ open_browser: Whether to open browser automatically
556
+ """
557
+ # Find metadata store
558
+ metadata_store_path = project_dir / ".dvt" / "metadata_store.duckdb"
559
+
560
+ if not metadata_store_path.exists():
561
+ if HAS_RICH:
562
+ console.print(Panel(
563
+ "[yellow]No metadata store found.[/yellow]\n\n"
564
+ "Run [bold cyan]dvt profile[/bold cyan] first to capture profiling data.",
565
+ title="[bold red]Error[/bold red]",
566
+ border_style="red",
567
+ ))
568
+ else:
569
+ print("Error: No metadata store found.")
570
+ print("Run 'dvt profile' first to capture profiling data.")
571
+ return False
572
+
573
+ # Create handler with metadata store path
574
+ def handler(*args, **kwargs):
575
+ return ProfileAPIHandler(*args, metadata_store_path=metadata_store_path, **kwargs)
576
+
577
+ # Start server
578
+ server = HTTPServer((host, port), handler)
579
+ url = f"http://{host}:{port}"
580
+
581
+ if HAS_RICH:
582
+ console.print()
583
+ console.print(Panel(
584
+ f"[bold green]Profile Viewer running at:[/bold green]\n\n"
585
+ f" [bold cyan]{url}[/bold cyan]\n\n"
586
+ f"[dim]Press Ctrl+C to stop[/dim]",
587
+ title="[bold magenta]🔍 DVT Profile Viewer[/bold magenta]",
588
+ border_style="magenta",
589
+ box=box.DOUBLE,
590
+ ))
591
+ console.print()
592
+ else:
593
+ print(f"\nDVT Profile Viewer running at: {url}")
594
+ print("Press Ctrl+C to stop\n")
595
+
596
+ # Open browser
597
+ if open_browser:
598
+ threading.Timer(1.0, lambda: webbrowser.open(url)).start()
599
+
600
+ try:
601
+ server.serve_forever()
602
+ except KeyboardInterrupt:
603
+ if HAS_RICH:
604
+ console.print("\n[yellow]Server stopped.[/yellow]")
605
+ else:
606
+ print("\nServer stopped.")
607
+
608
+ return True
609
+
610
+
611
+ if __name__ == "__main__":
612
+ # For testing
613
+ import sys
614
+ project_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
615
+ serve_profile_ui(project_dir)