dvt-core 0.59.0a51__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 (299) 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 +2660 -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 +60 -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.py +642 -0
  74. dbt/compute/federated_executor.py +1080 -0
  75. dbt/compute/filter_pushdown.py +273 -0
  76. dbt/compute/jar_provisioning.py +273 -0
  77. dbt/compute/java_compat.py +689 -0
  78. dbt/compute/jdbc_utils.py +1252 -0
  79. dbt/compute/metadata/__init__.py +63 -0
  80. dbt/compute/metadata/adapters_registry.py +370 -0
  81. dbt/compute/metadata/catalog_store.py +1036 -0
  82. dbt/compute/metadata/registry.py +674 -0
  83. dbt/compute/metadata/store.py +1020 -0
  84. dbt/compute/smart_selector.py +377 -0
  85. dbt/compute/spark_logger.py +272 -0
  86. dbt/compute/strategies/__init__.py +55 -0
  87. dbt/compute/strategies/base.py +165 -0
  88. dbt/compute/strategies/dataproc.py +207 -0
  89. dbt/compute/strategies/emr.py +203 -0
  90. dbt/compute/strategies/local.py +472 -0
  91. dbt/compute/strategies/standalone.py +262 -0
  92. dbt/config/__init__.py +4 -0
  93. dbt/config/catalogs.py +94 -0
  94. dbt/config/compute.py +513 -0
  95. dbt/config/dvt_profile.py +408 -0
  96. dbt/config/profile.py +422 -0
  97. dbt/config/project.py +888 -0
  98. dbt/config/project_utils.py +48 -0
  99. dbt/config/renderer.py +231 -0
  100. dbt/config/runtime.py +564 -0
  101. dbt/config/selectors.py +208 -0
  102. dbt/config/utils.py +77 -0
  103. dbt/constants.py +28 -0
  104. dbt/context/__init__.py +0 -0
  105. dbt/context/base.py +745 -0
  106. dbt/context/configured.py +135 -0
  107. dbt/context/context_config.py +382 -0
  108. dbt/context/docs.py +82 -0
  109. dbt/context/exceptions_jinja.py +178 -0
  110. dbt/context/macro_resolver.py +195 -0
  111. dbt/context/macros.py +171 -0
  112. dbt/context/manifest.py +72 -0
  113. dbt/context/providers.py +2249 -0
  114. dbt/context/query_header.py +13 -0
  115. dbt/context/secret.py +58 -0
  116. dbt/context/target.py +74 -0
  117. dbt/contracts/__init__.py +0 -0
  118. dbt/contracts/files.py +413 -0
  119. dbt/contracts/graph/__init__.py +0 -0
  120. dbt/contracts/graph/manifest.py +1904 -0
  121. dbt/contracts/graph/metrics.py +97 -0
  122. dbt/contracts/graph/model_config.py +70 -0
  123. dbt/contracts/graph/node_args.py +42 -0
  124. dbt/contracts/graph/nodes.py +1806 -0
  125. dbt/contracts/graph/semantic_manifest.py +232 -0
  126. dbt/contracts/graph/unparsed.py +811 -0
  127. dbt/contracts/project.py +419 -0
  128. dbt/contracts/results.py +53 -0
  129. dbt/contracts/selection.py +23 -0
  130. dbt/contracts/sql.py +85 -0
  131. dbt/contracts/state.py +68 -0
  132. dbt/contracts/util.py +46 -0
  133. dbt/deprecations.py +348 -0
  134. dbt/deps/__init__.py +0 -0
  135. dbt/deps/base.py +152 -0
  136. dbt/deps/git.py +195 -0
  137. dbt/deps/local.py +79 -0
  138. dbt/deps/registry.py +130 -0
  139. dbt/deps/resolver.py +149 -0
  140. dbt/deps/tarball.py +120 -0
  141. dbt/docs/source/_ext/dbt_click.py +119 -0
  142. dbt/docs/source/conf.py +32 -0
  143. dbt/env_vars.py +64 -0
  144. dbt/event_time/event_time.py +40 -0
  145. dbt/event_time/sample_window.py +60 -0
  146. dbt/events/__init__.py +15 -0
  147. dbt/events/base_types.py +36 -0
  148. dbt/events/core_types_pb2.py +2 -0
  149. dbt/events/logging.py +108 -0
  150. dbt/events/types.py +2516 -0
  151. dbt/exceptions.py +1486 -0
  152. dbt/flags.py +89 -0
  153. dbt/graph/__init__.py +11 -0
  154. dbt/graph/cli.py +249 -0
  155. dbt/graph/graph.py +172 -0
  156. dbt/graph/queue.py +214 -0
  157. dbt/graph/selector.py +374 -0
  158. dbt/graph/selector_methods.py +975 -0
  159. dbt/graph/selector_spec.py +222 -0
  160. dbt/graph/thread_pool.py +18 -0
  161. dbt/hooks.py +21 -0
  162. dbt/include/README.md +49 -0
  163. dbt/include/__init__.py +3 -0
  164. dbt/include/data/adapters_registry.duckdb +0 -0
  165. dbt/include/data/build_comprehensive_registry.py +1254 -0
  166. dbt/include/data/build_registry.py +242 -0
  167. dbt/include/data/csv/adapter_queries.csv +33 -0
  168. dbt/include/data/csv/syntax_rules.csv +9 -0
  169. dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
  170. dbt/include/data/csv/type_mappings_databricks.csv +30 -0
  171. dbt/include/data/csv/type_mappings_mysql.csv +40 -0
  172. dbt/include/data/csv/type_mappings_oracle.csv +30 -0
  173. dbt/include/data/csv/type_mappings_postgres.csv +56 -0
  174. dbt/include/data/csv/type_mappings_redshift.csv +33 -0
  175. dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
  176. dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
  177. dbt/include/dvt_starter_project/README.md +15 -0
  178. dbt/include/dvt_starter_project/__init__.py +3 -0
  179. dbt/include/dvt_starter_project/analyses/PLACEHOLDER +0 -0
  180. dbt/include/dvt_starter_project/dvt_project.yml +39 -0
  181. dbt/include/dvt_starter_project/logs/PLACEHOLDER +0 -0
  182. dbt/include/dvt_starter_project/macros/PLACEHOLDER +0 -0
  183. dbt/include/dvt_starter_project/models/example/my_first_dbt_model.sql +27 -0
  184. dbt/include/dvt_starter_project/models/example/my_second_dbt_model.sql +6 -0
  185. dbt/include/dvt_starter_project/models/example/schema.yml +21 -0
  186. dbt/include/dvt_starter_project/seeds/PLACEHOLDER +0 -0
  187. dbt/include/dvt_starter_project/snapshots/PLACEHOLDER +0 -0
  188. dbt/include/dvt_starter_project/tests/PLACEHOLDER +0 -0
  189. dbt/internal_deprecations.py +26 -0
  190. dbt/jsonschemas/__init__.py +3 -0
  191. dbt/jsonschemas/jsonschemas.py +309 -0
  192. dbt/jsonschemas/project/0.0.110.json +4717 -0
  193. dbt/jsonschemas/project/0.0.85.json +2015 -0
  194. dbt/jsonschemas/resources/0.0.110.json +2636 -0
  195. dbt/jsonschemas/resources/0.0.85.json +2536 -0
  196. dbt/jsonschemas/resources/latest.json +6773 -0
  197. dbt/links.py +4 -0
  198. dbt/materializations/__init__.py +0 -0
  199. dbt/materializations/incremental/__init__.py +0 -0
  200. dbt/materializations/incremental/microbatch.py +236 -0
  201. dbt/mp_context.py +8 -0
  202. dbt/node_types.py +37 -0
  203. dbt/parser/__init__.py +23 -0
  204. dbt/parser/analysis.py +21 -0
  205. dbt/parser/base.py +548 -0
  206. dbt/parser/common.py +266 -0
  207. dbt/parser/docs.py +52 -0
  208. dbt/parser/fixtures.py +51 -0
  209. dbt/parser/functions.py +30 -0
  210. dbt/parser/generic_test.py +100 -0
  211. dbt/parser/generic_test_builders.py +333 -0
  212. dbt/parser/hooks.py +122 -0
  213. dbt/parser/macros.py +137 -0
  214. dbt/parser/manifest.py +2208 -0
  215. dbt/parser/models.py +573 -0
  216. dbt/parser/partial.py +1178 -0
  217. dbt/parser/read_files.py +445 -0
  218. dbt/parser/schema_generic_tests.py +422 -0
  219. dbt/parser/schema_renderer.py +111 -0
  220. dbt/parser/schema_yaml_readers.py +935 -0
  221. dbt/parser/schemas.py +1466 -0
  222. dbt/parser/search.py +149 -0
  223. dbt/parser/seeds.py +28 -0
  224. dbt/parser/singular_test.py +20 -0
  225. dbt/parser/snapshots.py +44 -0
  226. dbt/parser/sources.py +558 -0
  227. dbt/parser/sql.py +62 -0
  228. dbt/parser/unit_tests.py +621 -0
  229. dbt/plugins/__init__.py +20 -0
  230. dbt/plugins/contracts.py +9 -0
  231. dbt/plugins/exceptions.py +2 -0
  232. dbt/plugins/manager.py +163 -0
  233. dbt/plugins/manifest.py +21 -0
  234. dbt/profiler.py +20 -0
  235. dbt/py.typed +1 -0
  236. dbt/query_analyzer.py +410 -0
  237. dbt/runners/__init__.py +2 -0
  238. dbt/runners/exposure_runner.py +7 -0
  239. dbt/runners/no_op_runner.py +45 -0
  240. dbt/runners/saved_query_runner.py +7 -0
  241. dbt/selected_resources.py +8 -0
  242. dbt/task/__init__.py +0 -0
  243. dbt/task/base.py +506 -0
  244. dbt/task/build.py +197 -0
  245. dbt/task/clean.py +56 -0
  246. dbt/task/clone.py +161 -0
  247. dbt/task/compile.py +150 -0
  248. dbt/task/compute.py +458 -0
  249. dbt/task/debug.py +513 -0
  250. dbt/task/deps.py +280 -0
  251. dbt/task/docs/__init__.py +3 -0
  252. dbt/task/docs/api/__init__.py +23 -0
  253. dbt/task/docs/api/catalog.py +204 -0
  254. dbt/task/docs/api/lineage.py +234 -0
  255. dbt/task/docs/api/profile.py +204 -0
  256. dbt/task/docs/api/spark.py +186 -0
  257. dbt/task/docs/generate.py +1002 -0
  258. dbt/task/docs/index.html +250 -0
  259. dbt/task/docs/serve.py +174 -0
  260. dbt/task/dvt_output.py +509 -0
  261. dbt/task/dvt_run.py +282 -0
  262. dbt/task/dvt_seed.py +806 -0
  263. dbt/task/freshness.py +322 -0
  264. dbt/task/function.py +121 -0
  265. dbt/task/group_lookup.py +46 -0
  266. dbt/task/init.py +1022 -0
  267. dbt/task/java.py +316 -0
  268. dbt/task/list.py +236 -0
  269. dbt/task/metadata.py +804 -0
  270. dbt/task/migrate.py +714 -0
  271. dbt/task/printer.py +175 -0
  272. dbt/task/profile.py +1489 -0
  273. dbt/task/profile_serve.py +662 -0
  274. dbt/task/retract.py +441 -0
  275. dbt/task/retry.py +175 -0
  276. dbt/task/run.py +1647 -0
  277. dbt/task/run_operation.py +141 -0
  278. dbt/task/runnable.py +758 -0
  279. dbt/task/seed.py +103 -0
  280. dbt/task/show.py +149 -0
  281. dbt/task/snapshot.py +56 -0
  282. dbt/task/spark.py +414 -0
  283. dbt/task/sql.py +110 -0
  284. dbt/task/target_sync.py +814 -0
  285. dbt/task/test.py +464 -0
  286. dbt/tests/fixtures/__init__.py +1 -0
  287. dbt/tests/fixtures/project.py +620 -0
  288. dbt/tests/util.py +651 -0
  289. dbt/tracking.py +529 -0
  290. dbt/utils/__init__.py +3 -0
  291. dbt/utils/artifact_upload.py +151 -0
  292. dbt/utils/utils.py +408 -0
  293. dbt/version.py +271 -0
  294. dvt_cli/__init__.py +158 -0
  295. dvt_core-0.59.0a51.dist-info/METADATA +288 -0
  296. dvt_core-0.59.0a51.dist-info/RECORD +299 -0
  297. dvt_core-0.59.0a51.dist-info/WHEEL +5 -0
  298. dvt_core-0.59.0a51.dist-info/entry_points.txt +2 -0
  299. dvt_core-0.59.0a51.dist-info/top_level.txt +2 -0
dbt/task/docs/serve.py ADDED
@@ -0,0 +1,174 @@
1
+ """
2
+ DVT Docs Serve Task
3
+
4
+ v0.56.0: Enhanced with FastAPI backend and 3-tab web UI.
5
+
6
+ Serves documentation with:
7
+ - Traditional static HTML docs (backward compatible)
8
+ - REST API for catalog, profiling, lineage, and Spark status
9
+ - 3-tab web UI: Catalog, Profiling, Spark Monitor
10
+ """
11
+
12
+ import os
13
+ import shutil
14
+ import webbrowser
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ import click
19
+
20
+ from dbt.task.base import ConfiguredTask
21
+ from dbt.task.docs import DOCS_INDEX_FILE_PATH
22
+
23
+
24
+ class ServeTask(ConfiguredTask):
25
+ """
26
+ Serve documentation with optional enhanced API.
27
+
28
+ When FastAPI is available, serves:
29
+ - /api/catalog/* - Catalog nodes and search
30
+ - /api/profile/* - Profile results and alerts
31
+ - /api/lineage/* - Lineage graph and traversal
32
+ - /api/spark/* - Spark status (local only)
33
+ - /* - Static documentation files
34
+
35
+ Falls back to simple HTTP server when FastAPI is not installed.
36
+ """
37
+
38
+ def run(self):
39
+ port = self.args.port
40
+ host = self.args.host
41
+ project_root = Path(self.config.project_root)
42
+ target_path = Path(self.config.project_target_path)
43
+
44
+ # Check if FastAPI is available for enhanced serving
45
+ if self._fastapi_available():
46
+ self._run_fastapi_server(host, port, project_root, target_path)
47
+ else:
48
+ self._run_simple_server(host, port, target_path)
49
+
50
+ def _fastapi_available(self) -> bool:
51
+ """Check if FastAPI and uvicorn are installed."""
52
+ try:
53
+ import fastapi # noqa: F401
54
+ import uvicorn # noqa: F401
55
+ return True
56
+ except ImportError:
57
+ return False
58
+
59
+ def _run_fastapi_server(
60
+ self,
61
+ host: str,
62
+ port: int,
63
+ project_root: Path,
64
+ target_path: Path,
65
+ ):
66
+ """Run enhanced FastAPI server with REST API."""
67
+ from fastapi import FastAPI
68
+ from fastapi.staticfiles import StaticFiles
69
+ from fastapi.responses import RedirectResponse
70
+ import uvicorn
71
+
72
+ # Import API routers
73
+ from dbt.task.docs.api import (
74
+ catalog_router,
75
+ profile_router,
76
+ lineage_router,
77
+ spark_router,
78
+ )
79
+ from dbt.task.docs.api import catalog, profile, lineage, spark
80
+
81
+ # Initialize metadata store ONCE at startup
82
+ try:
83
+ from dbt.compute.metadata import ProjectMetadataStore
84
+ store = ProjectMetadataStore(project_root)
85
+ store.initialize() # Only called once here
86
+ click.echo(f" Metadata store initialized: {project_root}/.dvt/metadata_store.duckdb")
87
+ except Exception as e:
88
+ click.echo(f" Warning: Could not initialize metadata store: {e}")
89
+
90
+ # Set project root for all API modules (store will skip re-initialization)
91
+ catalog.set_project_root(project_root)
92
+ profile.set_project_root(project_root)
93
+ lineage.set_project_root(project_root)
94
+ spark.set_project_root(project_root)
95
+
96
+ # Create FastAPI app
97
+ app = FastAPI(
98
+ title="DVT Documentation",
99
+ description="DVT catalog, profiling, and lineage API",
100
+ version="0.56.0",
101
+ )
102
+
103
+ # Include API routers
104
+ app.include_router(catalog_router)
105
+ app.include_router(profile_router)
106
+ app.include_router(lineage_router)
107
+ app.include_router(spark_router)
108
+
109
+ # Prepare static files directory
110
+ os.chdir(target_path)
111
+ shutil.copyfile(DOCS_INDEX_FILE_PATH, "index.html")
112
+
113
+ # Root redirect to index.html
114
+ @app.get("/")
115
+ async def root():
116
+ return RedirectResponse(url="/index.html")
117
+
118
+ # Mount static files (must be last to not override API routes)
119
+ app.mount("/", StaticFiles(directory=str(target_path), html=True), name="static")
120
+
121
+ # Open browser if requested
122
+ if self.args.browser:
123
+ webbrowser.open_new_tab(f"http://localhost:{port}")
124
+
125
+ # Print server info
126
+ click.echo("")
127
+ click.echo("╔════════════════════════════════════════════════════════════════╗")
128
+ click.echo("║ DVT Documentation Server (Enhanced) ║")
129
+ click.echo("╠════════════════════════════════════════════════════════════════╣")
130
+ click.echo(f"║ Serving at: http://{host}:{port}".ljust(66) + "║")
131
+ click.echo("║ ║")
132
+ click.echo("║ Web UI: ║")
133
+ click.echo(f"║ • Documentation: http://localhost:{port}/".ljust(66) + "║")
134
+ click.echo(f"║ • API Docs: http://localhost:{port}/docs".ljust(66) + "║")
135
+ click.echo("║ ║")
136
+ click.echo("║ API Endpoints: ║")
137
+ click.echo("║ • /api/catalog/* - Catalog nodes and search ║")
138
+ click.echo("║ • /api/profile/* - Profile results and alerts ║")
139
+ click.echo("║ • /api/lineage/* - Lineage graph and traversal ║")
140
+ click.echo("║ • /api/spark/* - Spark status (local only) ║")
141
+ click.echo("╠════════════════════════════════════════════════════════════════╣")
142
+ click.echo("║ Press Ctrl+C to stop ║")
143
+ click.echo("╚════════════════════════════════════════════════════════════════╝")
144
+ click.echo("")
145
+
146
+ # Run server
147
+ uvicorn.run(app, host=host, port=port, log_level="warning")
148
+
149
+ def _run_simple_server(self, host: str, port: int, target_path: Path):
150
+ """Run simple HTTP server (fallback when FastAPI not installed)."""
151
+ import socketserver
152
+ from http.server import SimpleHTTPRequestHandler
153
+
154
+ os.chdir(target_path)
155
+ shutil.copyfile(DOCS_INDEX_FILE_PATH, "index.html")
156
+
157
+ if self.args.browser:
158
+ webbrowser.open_new_tab(f"http://localhost:{port}")
159
+
160
+ click.echo("")
161
+ click.echo("╔════════════════════════════════════════════════════════════════╗")
162
+ click.echo("║ DVT Documentation Server (Basic) ║")
163
+ click.echo("╠════════════════════════════════════════════════════════════════╣")
164
+ click.echo(f"║ Serving at: http://{host}:{port}".ljust(66) + "║")
165
+ click.echo("║ ║")
166
+ click.echo("║ Note: Install fastapi and uvicorn for enhanced features: ║")
167
+ click.echo("║ pip install fastapi uvicorn ║")
168
+ click.echo("╠════════════════════════════════════════════════════════════════╣")
169
+ click.echo("║ Press Ctrl+C to stop ║")
170
+ click.echo("╚════════════════════════════════════════════════════════════════╝")
171
+ click.echo("")
172
+
173
+ with socketserver.TCPServer((host, port), SimpleHTTPRequestHandler) as httpd:
174
+ httpd.serve_forever()
dbt/task/dvt_output.py ADDED
@@ -0,0 +1,509 @@
1
+ # =============================================================================
2
+ # DVT Rich Output Helpers
3
+ # =============================================================================
4
+ # Beautiful CLI output using Rich library for DVT commands.
5
+ #
6
+ # DVT v0.58.0: Unified output styling for all DVT commands
7
+ # DVT v0.58.1: Enhanced for dvt run integration
8
+ # DVT v0.59.0a36: Multi-bar progress display for all operations
9
+ # =============================================================================
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ import threading
15
+ from dataclasses import dataclass, field
16
+ from typing import Any, Dict, List, Optional, Tuple
17
+ from enum import Enum
18
+
19
+ # Try to import Rich - graceful fallback if not available
20
+ try:
21
+ from rich.console import Console
22
+ from rich.panel import Panel
23
+ from rich import box
24
+ HAS_RICH = True
25
+ except ImportError:
26
+ HAS_RICH = False
27
+ Console = None # type: ignore
28
+
29
+
30
+ class ModelStatus(Enum):
31
+ """Status for a model in the progress display."""
32
+ PENDING = "pending"
33
+ RUNNING = "running"
34
+ PASSED = "passed"
35
+ FAILED = "failed"
36
+ SKIPPED = "skipped"
37
+ WARNED = "warned"
38
+
39
+
40
+ # Status icons for display
41
+ STATUS_ICONS = {
42
+ ModelStatus.PENDING: "○",
43
+ ModelStatus.RUNNING: "⏳",
44
+ ModelStatus.PASSED: "✓",
45
+ ModelStatus.FAILED: "✗",
46
+ ModelStatus.SKIPPED: "⊘",
47
+ ModelStatus.WARNED: "⚠",
48
+ }
49
+
50
+ # Operation icons for header
51
+ OPERATION_ICONS = {
52
+ "run": "▶",
53
+ "build": "🔧",
54
+ "seed": "🌱",
55
+ "profile": "📊",
56
+ "test": "✓",
57
+ "compile": "📝",
58
+ "snapshot": "📸",
59
+ "retract": "🗑",
60
+ }
61
+
62
+
63
+ @dataclass
64
+ class ModelInfo:
65
+ """Information about a model for multi-bar display."""
66
+ name: str
67
+ unique_id: str
68
+ materialization: str = "table"
69
+ status: ModelStatus = ModelStatus.PENDING
70
+ execution_path: str = ""
71
+ duration_ms: float = 0.0
72
+ warning: str = "" # e.g., "view→table" for coercion
73
+ error_message: str = ""
74
+
75
+
76
+ @dataclass
77
+ class ErrorInfo:
78
+ """Error information for summary display."""
79
+ model_name: str
80
+ message: str
81
+ duration_ms: float
82
+ execution_path: str
83
+
84
+
85
+ @dataclass
86
+ class DVTRunStats:
87
+ """Statistics for a DVT run execution."""
88
+ total: int = 0
89
+ passed: int = 0
90
+ failed: int = 0
91
+ skipped: int = 0
92
+ warned: int = 0
93
+ errored: int = 0
94
+ start_time: float = field(default_factory=time.time)
95
+ end_time: float = 0.0
96
+ pushdown_count: int = 0
97
+ federation_count: int = 0
98
+
99
+ @property
100
+ def duration_seconds(self) -> float:
101
+ return self.end_time - self.start_time if self.end_time else 0.0
102
+
103
+ @property
104
+ def success_rate(self) -> float:
105
+ if self.total == 0:
106
+ return 100.0
107
+ return (self.passed / self.total) * 100
108
+
109
+
110
+ # =============================================================================
111
+ # DVTMultiBarDisplay - Summary-focused display (v0.59.0a36)
112
+ # =============================================================================
113
+ #
114
+ # NOTE: Rich's Live context conflicts with dbt's event logging system.
115
+ # dbt fires events via fire_event() which write directly to stdout, bypassing
116
+ # Rich's Live display context. This causes display corruption.
117
+ #
118
+ # SOLUTION: Instead of live multi-bar updates, we:
119
+ # 1. Print a header panel at the start (one-time)
120
+ # 2. Let dbt's normal event output flow during execution
121
+ # 3. Track model results internally (no live display updates)
122
+ # 4. Print a beautiful summary panel at the end with DVT-specific info
123
+ #
124
+ # This approach works WITH dbt's event system rather than fighting it.
125
+ # =============================================================================
126
+
127
+ class DVTMultiBarDisplay:
128
+ """
129
+ Enhanced progress display for DVT commands with summary focus.
130
+
131
+ Features:
132
+ - Header panel with operation, target, and compute (printed at start)
133
+ - Track model results as they complete
134
+ - Warning collection for materialization coercions
135
+ - Summary screen with errors grouped by execution path
136
+
137
+ DVT v0.59.0a36: Works with dbt's event system - header + summary approach.
138
+
139
+ Note: Does NOT use Rich's Live context due to conflicts with dbt's
140
+ fire_event logging. Instead, lets dbt output flow normally and provides
141
+ enhanced header and summary panels.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ title: str = "DVT Run",
147
+ operation: str = "run",
148
+ target: str = "",
149
+ compute: str = "",
150
+ ):
151
+ self.title = title
152
+ self.operation = operation
153
+ self.target = target
154
+ self.compute = compute
155
+ self._use_rich = HAS_RICH
156
+ self._console: Optional[Console] = None
157
+ self._models: Dict[str, ModelInfo] = {} # unique_id -> ModelInfo
158
+ self._errors_pushdown: List[ErrorInfo] = []
159
+ self._errors_federation: List[ErrorInfo] = []
160
+ self._warnings: List[Tuple[str, str]] = [] # (model_name, warning)
161
+ self._lock = threading.Lock()
162
+ self.stats = DVTRunStats()
163
+ self._header_printed = False
164
+
165
+ if self._use_rich:
166
+ self._console = Console()
167
+
168
+ def _build_header(self) -> Panel:
169
+ """Build the header panel with operation, target, and compute."""
170
+ op_icon = OPERATION_ICONS.get(self.operation.lower(), "▶")
171
+
172
+ info_parts = [
173
+ f"[bold cyan]Operation:[/bold cyan] [bold yellow]{op_icon} {self.operation.upper()}[/bold yellow]",
174
+ ]
175
+ if self.target:
176
+ info_parts.append(f"[bold cyan]Target:[/bold cyan] [yellow]{self.target}[/yellow]")
177
+ if self.compute:
178
+ info_parts.append(f"[bold cyan]Compute:[/bold cyan] [yellow]{self.compute}[/yellow]")
179
+
180
+ info_line = " │ ".join(info_parts)
181
+
182
+ return Panel(
183
+ info_line,
184
+ title=f"[bold magenta]{self.title}[/bold magenta]",
185
+ border_style="magenta",
186
+ box=box.DOUBLE,
187
+ )
188
+
189
+ def initialize_models(self, nodes: List[Any]) -> None:
190
+ """
191
+ Initialize model tracking (no visual progress bars).
192
+
193
+ Args:
194
+ nodes: List of ResultNode objects from _flattened_nodes
195
+ """
196
+ for node in nodes:
197
+ # Get model info from node
198
+ unique_id = getattr(node, 'unique_id', str(node))
199
+ name = getattr(node, 'name', unique_id.split('.')[-1])
200
+
201
+ # Get materialization from config
202
+ config = getattr(node, 'config', None)
203
+ materialization = "table"
204
+ if config:
205
+ materialization = getattr(config, 'materialized', 'table') or 'table'
206
+
207
+ # Skip ephemeral models
208
+ if materialization == "ephemeral":
209
+ continue
210
+
211
+ # Create ModelInfo for tracking
212
+ model_info = ModelInfo(
213
+ name=name,
214
+ unique_id=unique_id,
215
+ materialization=materialization,
216
+ )
217
+ self._models[unique_id] = model_info
218
+
219
+ self.stats.total = len(self._models)
220
+
221
+ def start_display(self) -> None:
222
+ """Print the header panel (one-time, before execution starts)."""
223
+ if not self._use_rich or self._header_printed:
224
+ return
225
+
226
+ self.stats.start_time = time.time()
227
+
228
+ # Print header panel immediately
229
+ header = self._build_header()
230
+ self._console.print(header)
231
+ self._console.print() # Add blank line before dbt output
232
+ self._header_printed = True
233
+
234
+ def update_model_start(self, unique_id: str) -> None:
235
+ """Mark a model as running (no visual update - dbt handles this)."""
236
+ with self._lock:
237
+ if unique_id in self._models:
238
+ self._models[unique_id].status = ModelStatus.RUNNING
239
+
240
+ def update_model_complete(
241
+ self,
242
+ unique_id: str,
243
+ status: str,
244
+ duration_ms: float,
245
+ execution_path: str,
246
+ error_message: Optional[str] = None,
247
+ warning: Optional[str] = None,
248
+ ) -> None:
249
+ """
250
+ Track a model's completion (no live visual update).
251
+
252
+ dbt's event system shows progress during execution.
253
+ We track results for the summary panel.
254
+
255
+ Args:
256
+ unique_id: Model unique ID
257
+ status: Result status (pass, fail, skip, warn, error)
258
+ duration_ms: Execution time in milliseconds
259
+ execution_path: PUSHDOWN or FEDERATION
260
+ error_message: Error message if failed
261
+ warning: Warning message (e.g., materialization coercion)
262
+ """
263
+ with self._lock:
264
+ # Map status string to ModelStatus
265
+ status_map = {
266
+ "pass": ModelStatus.PASSED,
267
+ "success": ModelStatus.PASSED,
268
+ "fail": ModelStatus.FAILED,
269
+ "error": ModelStatus.FAILED,
270
+ "skip": ModelStatus.SKIPPED,
271
+ "warn": ModelStatus.WARNED,
272
+ }
273
+ model_status = status_map.get(status.lower(), ModelStatus.PASSED)
274
+
275
+ # Update model info
276
+ if unique_id in self._models:
277
+ model_info = self._models[unique_id]
278
+ model_info.status = model_status
279
+ model_info.duration_ms = duration_ms
280
+ model_info.execution_path = execution_path
281
+ model_info.warning = warning or ""
282
+ model_info.error_message = error_message or ""
283
+ model_name = model_info.name
284
+ else:
285
+ model_name = unique_id.split('.')[-1]
286
+
287
+ # Update stats
288
+ self._update_stats(status, execution_path)
289
+
290
+ # Collect errors
291
+ if error_message and model_status == ModelStatus.FAILED:
292
+ error_info = ErrorInfo(model_name, error_message, duration_ms, execution_path)
293
+ if execution_path == "PUSHDOWN":
294
+ self._errors_pushdown.append(error_info)
295
+ else:
296
+ self._errors_federation.append(error_info)
297
+
298
+ # Collect warnings
299
+ if warning:
300
+ self._warnings.append((model_name, warning))
301
+
302
+ def _update_stats(self, status: str, execution_path: str) -> None:
303
+ """Update stats counters."""
304
+ status_lower = status.lower()
305
+ if status_lower in ("pass", "success"):
306
+ self.stats.passed += 1
307
+ elif status_lower in ("fail", "error"):
308
+ self.stats.errored += 1
309
+ elif status_lower == "skip":
310
+ self.stats.skipped += 1
311
+ elif status_lower == "warn":
312
+ self.stats.warned += 1
313
+
314
+ if execution_path == "PUSHDOWN":
315
+ self.stats.pushdown_count += 1
316
+ elif execution_path == "FEDERATION":
317
+ self.stats.federation_count += 1
318
+
319
+ def stop_display(self) -> None:
320
+ """Record end time (no Live context to stop)."""
321
+ self.stats.end_time = time.time()
322
+
323
+ def print_summary(self) -> None:
324
+ """Print the summary panel with errors grouped by execution path."""
325
+ if not self._use_rich:
326
+ self._print_summary_fallback()
327
+ return
328
+
329
+ duration = self.stats.duration_seconds
330
+
331
+ # Build summary content
332
+ if self.stats.errored > 0:
333
+ status_text = f"[bold red]✗ Completed with {self.stats.errored} error(s)[/bold red]"
334
+ border_color = "red"
335
+ elif self.stats.warned > 0 or self._warnings:
336
+ status_text = f"[bold yellow]⚠ Completed with warnings[/bold yellow]"
337
+ border_color = "yellow"
338
+ else:
339
+ status_text = f"[bold green]✓ Completed successfully[/bold green]"
340
+ border_color = "green"
341
+
342
+ # Stats line
343
+ stats_parts = [f"[bold]Passed:[/bold] {self.stats.passed}"]
344
+ if self.stats.errored:
345
+ stats_parts.append(f"[bold red]Failed:[/bold red] {self.stats.errored}")
346
+ if self.stats.skipped:
347
+ stats_parts.append(f"[bold yellow]Skipped:[/bold yellow] {self.stats.skipped}")
348
+ if self._warnings:
349
+ stats_parts.append(f"[bold yellow]Warnings:[/bold yellow] {len(self._warnings)}")
350
+ stats_line = " │ ".join(stats_parts)
351
+
352
+ # Execution path counts
353
+ path_parts = []
354
+ if self.stats.pushdown_count:
355
+ path_parts.append(f"[blue]Pushdown:[/blue] {self.stats.pushdown_count}")
356
+ if self.stats.federation_count:
357
+ path_parts.append(f"[magenta]Federation:[/magenta] {self.stats.federation_count}")
358
+ path_parts.append(f"[dim]Duration:[/dim] {duration:.1f}s")
359
+ path_line = " │ ".join(path_parts)
360
+
361
+ summary_content = f"{status_text}\n\n{stats_line}\n{path_line}"
362
+
363
+ # Print main summary panel
364
+ self._console.print()
365
+ summary_panel = Panel(
366
+ summary_content,
367
+ title=f"[bold]{self.title} Complete[/bold]",
368
+ border_style=border_color,
369
+ box=box.ROUNDED,
370
+ )
371
+ self._console.print(summary_panel)
372
+
373
+ # Print warnings panel if any
374
+ if self._warnings:
375
+ self._print_warnings_panel()
376
+
377
+ # Print error panels grouped by path
378
+ if self._errors_pushdown:
379
+ self._print_error_panel("Pushdown Errors", self._errors_pushdown, "blue")
380
+ if self._errors_federation:
381
+ self._print_error_panel("Federation Errors", self._errors_federation, "magenta")
382
+
383
+ self._console.print()
384
+
385
+ def _print_warnings_panel(self) -> None:
386
+ """Print materialization warnings panel."""
387
+ lines = []
388
+ for model_name, warning in self._warnings[:10]:
389
+ lines.append(f" • [bold]{model_name}:[/bold] {warning}")
390
+ if len(self._warnings) > 10:
391
+ lines.append(f" [dim]... and {len(self._warnings) - 10} more[/dim]")
392
+
393
+ content = "\n".join(lines)
394
+ panel = Panel(
395
+ content,
396
+ title=f"[bold yellow]⚠ Materialization Warnings ({len(self._warnings)})[/bold yellow]",
397
+ border_style="yellow",
398
+ box=box.ROUNDED,
399
+ )
400
+ self._console.print(panel)
401
+
402
+ def _print_error_panel(self, title: str, errors: List[ErrorInfo], color: str) -> None:
403
+ """Print an error panel for a specific execution path."""
404
+ lines = []
405
+ for err in errors[:10]:
406
+ time_str = f"{err.duration_ms / 1000:.1f}s" if err.duration_ms >= 1000 else f"{err.duration_ms:.0f}ms"
407
+ # Extract error core
408
+ error_core = self._extract_error_core(err.message)
409
+ lines.append(f" • [bold]{err.model_name}[/bold] ({time_str}): {error_core}")
410
+ if len(errors) > 10:
411
+ lines.append(f" [dim]... and {len(errors) - 10} more[/dim]")
412
+
413
+ content = "\n".join(lines)
414
+ panel = Panel(
415
+ content,
416
+ title=f"[bold red]{title} ({len(errors)})[/bold red]",
417
+ border_style="red",
418
+ box=box.ROUNDED,
419
+ )
420
+ self._console.print(panel)
421
+
422
+ def _extract_error_core(self, error_message: str, max_len: int = 100) -> str:
423
+ """Extract the most useful part of an error message."""
424
+ if not error_message:
425
+ return "Unknown error"
426
+
427
+ msg = error_message.strip()
428
+ lines = msg.split('\n')
429
+
430
+ # Filter Java stack traces
431
+ filtered = []
432
+ for line in lines:
433
+ line = line.strip()
434
+ if not line:
435
+ continue
436
+ if line.startswith('at ') or line.startswith('... '):
437
+ continue
438
+ if 'org.apache.' in line or 'java.lang.' in line or 'scala.' in line:
439
+ if ': ' in line:
440
+ filtered.append(line.split(': ', 1)[-1])
441
+ continue
442
+ filtered.append(line)
443
+
444
+ result = ' | '.join(filtered[:2]) if filtered else msg[:max_len]
445
+ if len(result) > max_len:
446
+ result = result[:max_len] + "..."
447
+ return result
448
+
449
+ def _print_summary_fallback(self) -> None:
450
+ """Print summary without Rich."""
451
+ duration = self.stats.duration_seconds
452
+ print(f"\n{'=' * 60}")
453
+ print(f" {self.title} Complete")
454
+ print(f" Passed: {self.stats.passed} | Failed: {self.stats.errored} | Skipped: {self.stats.skipped}")
455
+ print(f" Pushdown: {self.stats.pushdown_count} | Federation: {self.stats.federation_count}")
456
+ print(f" Duration: {duration:.1f}s")
457
+ print(f"{'=' * 60}\n")
458
+
459
+
460
+ # =============================================================================
461
+ # Factory function
462
+ # =============================================================================
463
+
464
+ def create_dvt_display(
465
+ operation: str = "run",
466
+ target: str = "",
467
+ compute: str = "",
468
+ ) -> DVTMultiBarDisplay:
469
+ """
470
+ Factory function to create a DVT multi-bar display.
471
+
472
+ Args:
473
+ operation: Operation type (run, build, seed, profile, test, etc.)
474
+ target: Target name
475
+ compute: Compute engine name
476
+
477
+ Returns:
478
+ DVTMultiBarDisplay instance
479
+
480
+ Usage:
481
+ display = create_dvt_display(operation="run", target="postgres", compute="spark-local")
482
+ display.initialize_models(nodes)
483
+ display.start_display()
484
+ for result in results:
485
+ display.update_model_complete(...)
486
+ display.stop_display()
487
+ display.print_summary()
488
+ """
489
+ title_map = {
490
+ "run": "DVT Run",
491
+ "build": "DVT Build",
492
+ "seed": "DVT Seed",
493
+ "profile": "DVT Profile",
494
+ "test": "DVT Test",
495
+ "compile": "DVT Compile",
496
+ "snapshot": "DVT Snapshot",
497
+ "retract": "DVT Retract",
498
+ }
499
+
500
+ return DVTMultiBarDisplay(
501
+ title=title_map.get(operation.lower(), f"DVT {operation.title()}"),
502
+ operation=operation,
503
+ target=target,
504
+ compute=compute,
505
+ )
506
+
507
+
508
+ # Alias for backwards compatibility in imports (but different class)
509
+ DVTProgressDisplay = DVTMultiBarDisplay