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.
- dbt/__init__.py +7 -0
- dbt/_pydantic_shim.py +26 -0
- dbt/artifacts/__init__.py +0 -0
- dbt/artifacts/exceptions/__init__.py +1 -0
- dbt/artifacts/exceptions/schemas.py +31 -0
- dbt/artifacts/resources/__init__.py +116 -0
- dbt/artifacts/resources/base.py +67 -0
- dbt/artifacts/resources/types.py +93 -0
- dbt/artifacts/resources/v1/analysis.py +10 -0
- dbt/artifacts/resources/v1/catalog.py +23 -0
- dbt/artifacts/resources/v1/components.py +274 -0
- dbt/artifacts/resources/v1/config.py +277 -0
- dbt/artifacts/resources/v1/documentation.py +11 -0
- dbt/artifacts/resources/v1/exposure.py +51 -0
- dbt/artifacts/resources/v1/function.py +52 -0
- dbt/artifacts/resources/v1/generic_test.py +31 -0
- dbt/artifacts/resources/v1/group.py +21 -0
- dbt/artifacts/resources/v1/hook.py +11 -0
- dbt/artifacts/resources/v1/macro.py +29 -0
- dbt/artifacts/resources/v1/metric.py +172 -0
- dbt/artifacts/resources/v1/model.py +145 -0
- dbt/artifacts/resources/v1/owner.py +10 -0
- dbt/artifacts/resources/v1/saved_query.py +111 -0
- dbt/artifacts/resources/v1/seed.py +41 -0
- dbt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dbt/artifacts/resources/v1/semantic_model.py +314 -0
- dbt/artifacts/resources/v1/singular_test.py +14 -0
- dbt/artifacts/resources/v1/snapshot.py +91 -0
- dbt/artifacts/resources/v1/source_definition.py +84 -0
- dbt/artifacts/resources/v1/sql_operation.py +10 -0
- dbt/artifacts/resources/v1/unit_test_definition.py +77 -0
- dbt/artifacts/schemas/__init__.py +0 -0
- dbt/artifacts/schemas/base.py +191 -0
- dbt/artifacts/schemas/batch_results.py +24 -0
- dbt/artifacts/schemas/catalog/__init__.py +11 -0
- dbt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dbt/artifacts/schemas/catalog/v1/catalog.py +59 -0
- dbt/artifacts/schemas/freshness/__init__.py +1 -0
- dbt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dbt/artifacts/schemas/freshness/v3/freshness.py +158 -0
- dbt/artifacts/schemas/manifest/__init__.py +2 -0
- dbt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dbt/artifacts/schemas/manifest/v12/manifest.py +211 -0
- dbt/artifacts/schemas/results.py +147 -0
- dbt/artifacts/schemas/run/__init__.py +2 -0
- dbt/artifacts/schemas/run/v5/__init__.py +0 -0
- dbt/artifacts/schemas/run/v5/run.py +184 -0
- dbt/artifacts/schemas/upgrades/__init__.py +4 -0
- dbt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dbt/artifacts/utils/validation.py +153 -0
- dbt/cli/__init__.py +1 -0
- dbt/cli/context.py +17 -0
- dbt/cli/exceptions.py +57 -0
- dbt/cli/flags.py +560 -0
- dbt/cli/main.py +2660 -0
- dbt/cli/option_types.py +121 -0
- dbt/cli/options.py +80 -0
- dbt/cli/params.py +844 -0
- dbt/cli/requires.py +490 -0
- dbt/cli/resolvers.py +60 -0
- dbt/cli/types.py +40 -0
- dbt/clients/__init__.py +0 -0
- dbt/clients/checked_load.py +83 -0
- dbt/clients/git.py +164 -0
- dbt/clients/jinja.py +206 -0
- dbt/clients/jinja_static.py +245 -0
- dbt/clients/registry.py +192 -0
- dbt/clients/yaml_helper.py +68 -0
- dbt/compilation.py +876 -0
- dbt/compute/__init__.py +14 -0
- dbt/compute/engines/__init__.py +12 -0
- dbt/compute/engines/spark_engine.py +642 -0
- dbt/compute/federated_executor.py +1080 -0
- dbt/compute/filter_pushdown.py +273 -0
- dbt/compute/jar_provisioning.py +273 -0
- dbt/compute/java_compat.py +689 -0
- dbt/compute/jdbc_utils.py +1252 -0
- dbt/compute/metadata/__init__.py +63 -0
- dbt/compute/metadata/adapters_registry.py +370 -0
- dbt/compute/metadata/catalog_store.py +1036 -0
- dbt/compute/metadata/registry.py +674 -0
- dbt/compute/metadata/store.py +1020 -0
- dbt/compute/smart_selector.py +377 -0
- dbt/compute/spark_logger.py +272 -0
- dbt/compute/strategies/__init__.py +55 -0
- dbt/compute/strategies/base.py +165 -0
- dbt/compute/strategies/dataproc.py +207 -0
- dbt/compute/strategies/emr.py +203 -0
- dbt/compute/strategies/local.py +472 -0
- dbt/compute/strategies/standalone.py +262 -0
- dbt/config/__init__.py +4 -0
- dbt/config/catalogs.py +94 -0
- dbt/config/compute.py +513 -0
- dbt/config/dvt_profile.py +408 -0
- dbt/config/profile.py +422 -0
- dbt/config/project.py +888 -0
- dbt/config/project_utils.py +48 -0
- dbt/config/renderer.py +231 -0
- dbt/config/runtime.py +564 -0
- dbt/config/selectors.py +208 -0
- dbt/config/utils.py +77 -0
- dbt/constants.py +28 -0
- dbt/context/__init__.py +0 -0
- dbt/context/base.py +745 -0
- dbt/context/configured.py +135 -0
- dbt/context/context_config.py +382 -0
- dbt/context/docs.py +82 -0
- dbt/context/exceptions_jinja.py +178 -0
- dbt/context/macro_resolver.py +195 -0
- dbt/context/macros.py +171 -0
- dbt/context/manifest.py +72 -0
- dbt/context/providers.py +2249 -0
- dbt/context/query_header.py +13 -0
- dbt/context/secret.py +58 -0
- dbt/context/target.py +74 -0
- dbt/contracts/__init__.py +0 -0
- dbt/contracts/files.py +413 -0
- dbt/contracts/graph/__init__.py +0 -0
- dbt/contracts/graph/manifest.py +1904 -0
- dbt/contracts/graph/metrics.py +97 -0
- dbt/contracts/graph/model_config.py +70 -0
- dbt/contracts/graph/node_args.py +42 -0
- dbt/contracts/graph/nodes.py +1806 -0
- dbt/contracts/graph/semantic_manifest.py +232 -0
- dbt/contracts/graph/unparsed.py +811 -0
- dbt/contracts/project.py +419 -0
- dbt/contracts/results.py +53 -0
- dbt/contracts/selection.py +23 -0
- dbt/contracts/sql.py +85 -0
- dbt/contracts/state.py +68 -0
- dbt/contracts/util.py +46 -0
- dbt/deprecations.py +348 -0
- dbt/deps/__init__.py +0 -0
- dbt/deps/base.py +152 -0
- dbt/deps/git.py +195 -0
- dbt/deps/local.py +79 -0
- dbt/deps/registry.py +130 -0
- dbt/deps/resolver.py +149 -0
- dbt/deps/tarball.py +120 -0
- dbt/docs/source/_ext/dbt_click.py +119 -0
- dbt/docs/source/conf.py +32 -0
- dbt/env_vars.py +64 -0
- dbt/event_time/event_time.py +40 -0
- dbt/event_time/sample_window.py +60 -0
- dbt/events/__init__.py +15 -0
- dbt/events/base_types.py +36 -0
- dbt/events/core_types_pb2.py +2 -0
- dbt/events/logging.py +108 -0
- dbt/events/types.py +2516 -0
- dbt/exceptions.py +1486 -0
- dbt/flags.py +89 -0
- dbt/graph/__init__.py +11 -0
- dbt/graph/cli.py +249 -0
- dbt/graph/graph.py +172 -0
- dbt/graph/queue.py +214 -0
- dbt/graph/selector.py +374 -0
- dbt/graph/selector_methods.py +975 -0
- dbt/graph/selector_spec.py +222 -0
- dbt/graph/thread_pool.py +18 -0
- dbt/hooks.py +21 -0
- dbt/include/README.md +49 -0
- dbt/include/__init__.py +3 -0
- dbt/include/data/adapters_registry.duckdb +0 -0
- dbt/include/data/build_comprehensive_registry.py +1254 -0
- dbt/include/data/build_registry.py +242 -0
- dbt/include/data/csv/adapter_queries.csv +33 -0
- dbt/include/data/csv/syntax_rules.csv +9 -0
- dbt/include/data/csv/type_mappings_bigquery.csv +28 -0
- dbt/include/data/csv/type_mappings_databricks.csv +30 -0
- dbt/include/data/csv/type_mappings_mysql.csv +40 -0
- dbt/include/data/csv/type_mappings_oracle.csv +30 -0
- dbt/include/data/csv/type_mappings_postgres.csv +56 -0
- dbt/include/data/csv/type_mappings_redshift.csv +33 -0
- dbt/include/data/csv/type_mappings_snowflake.csv +38 -0
- dbt/include/data/csv/type_mappings_sqlserver.csv +35 -0
- dbt/include/dvt_starter_project/README.md +15 -0
- dbt/include/dvt_starter_project/__init__.py +3 -0
- dbt/include/dvt_starter_project/analyses/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/dvt_project.yml +39 -0
- dbt/include/dvt_starter_project/logs/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/macros/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/models/example/my_first_dbt_model.sql +27 -0
- dbt/include/dvt_starter_project/models/example/my_second_dbt_model.sql +6 -0
- dbt/include/dvt_starter_project/models/example/schema.yml +21 -0
- dbt/include/dvt_starter_project/seeds/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/snapshots/PLACEHOLDER +0 -0
- dbt/include/dvt_starter_project/tests/PLACEHOLDER +0 -0
- dbt/internal_deprecations.py +26 -0
- dbt/jsonschemas/__init__.py +3 -0
- dbt/jsonschemas/jsonschemas.py +309 -0
- dbt/jsonschemas/project/0.0.110.json +4717 -0
- dbt/jsonschemas/project/0.0.85.json +2015 -0
- dbt/jsonschemas/resources/0.0.110.json +2636 -0
- dbt/jsonschemas/resources/0.0.85.json +2536 -0
- dbt/jsonschemas/resources/latest.json +6773 -0
- dbt/links.py +4 -0
- dbt/materializations/__init__.py +0 -0
- dbt/materializations/incremental/__init__.py +0 -0
- dbt/materializations/incremental/microbatch.py +236 -0
- dbt/mp_context.py +8 -0
- dbt/node_types.py +37 -0
- dbt/parser/__init__.py +23 -0
- dbt/parser/analysis.py +21 -0
- dbt/parser/base.py +548 -0
- dbt/parser/common.py +266 -0
- dbt/parser/docs.py +52 -0
- dbt/parser/fixtures.py +51 -0
- dbt/parser/functions.py +30 -0
- dbt/parser/generic_test.py +100 -0
- dbt/parser/generic_test_builders.py +333 -0
- dbt/parser/hooks.py +122 -0
- dbt/parser/macros.py +137 -0
- dbt/parser/manifest.py +2208 -0
- dbt/parser/models.py +573 -0
- dbt/parser/partial.py +1178 -0
- dbt/parser/read_files.py +445 -0
- dbt/parser/schema_generic_tests.py +422 -0
- dbt/parser/schema_renderer.py +111 -0
- dbt/parser/schema_yaml_readers.py +935 -0
- dbt/parser/schemas.py +1466 -0
- dbt/parser/search.py +149 -0
- dbt/parser/seeds.py +28 -0
- dbt/parser/singular_test.py +20 -0
- dbt/parser/snapshots.py +44 -0
- dbt/parser/sources.py +558 -0
- dbt/parser/sql.py +62 -0
- dbt/parser/unit_tests.py +621 -0
- dbt/plugins/__init__.py +20 -0
- dbt/plugins/contracts.py +9 -0
- dbt/plugins/exceptions.py +2 -0
- dbt/plugins/manager.py +163 -0
- dbt/plugins/manifest.py +21 -0
- dbt/profiler.py +20 -0
- dbt/py.typed +1 -0
- dbt/query_analyzer.py +410 -0
- dbt/runners/__init__.py +2 -0
- dbt/runners/exposure_runner.py +7 -0
- dbt/runners/no_op_runner.py +45 -0
- dbt/runners/saved_query_runner.py +7 -0
- dbt/selected_resources.py +8 -0
- dbt/task/__init__.py +0 -0
- dbt/task/base.py +506 -0
- dbt/task/build.py +197 -0
- dbt/task/clean.py +56 -0
- dbt/task/clone.py +161 -0
- dbt/task/compile.py +150 -0
- dbt/task/compute.py +458 -0
- dbt/task/debug.py +513 -0
- dbt/task/deps.py +280 -0
- dbt/task/docs/__init__.py +3 -0
- dbt/task/docs/api/__init__.py +23 -0
- dbt/task/docs/api/catalog.py +204 -0
- dbt/task/docs/api/lineage.py +234 -0
- dbt/task/docs/api/profile.py +204 -0
- dbt/task/docs/api/spark.py +186 -0
- dbt/task/docs/generate.py +1002 -0
- dbt/task/docs/index.html +250 -0
- dbt/task/docs/serve.py +174 -0
- dbt/task/dvt_output.py +509 -0
- dbt/task/dvt_run.py +282 -0
- dbt/task/dvt_seed.py +806 -0
- dbt/task/freshness.py +322 -0
- dbt/task/function.py +121 -0
- dbt/task/group_lookup.py +46 -0
- dbt/task/init.py +1022 -0
- dbt/task/java.py +316 -0
- dbt/task/list.py +236 -0
- dbt/task/metadata.py +804 -0
- dbt/task/migrate.py +714 -0
- dbt/task/printer.py +175 -0
- dbt/task/profile.py +1489 -0
- dbt/task/profile_serve.py +662 -0
- dbt/task/retract.py +441 -0
- dbt/task/retry.py +175 -0
- dbt/task/run.py +1647 -0
- dbt/task/run_operation.py +141 -0
- dbt/task/runnable.py +758 -0
- dbt/task/seed.py +103 -0
- dbt/task/show.py +149 -0
- dbt/task/snapshot.py +56 -0
- dbt/task/spark.py +414 -0
- dbt/task/sql.py +110 -0
- dbt/task/target_sync.py +814 -0
- dbt/task/test.py +464 -0
- dbt/tests/fixtures/__init__.py +1 -0
- dbt/tests/fixtures/project.py +620 -0
- dbt/tests/util.py +651 -0
- dbt/tracking.py +529 -0
- dbt/utils/__init__.py +3 -0
- dbt/utils/artifact_upload.py +151 -0
- dbt/utils/utils.py +408 -0
- dbt/version.py +271 -0
- dvt_cli/__init__.py +158 -0
- dvt_core-0.59.0a51.dist-info/METADATA +288 -0
- dvt_core-0.59.0a51.dist-info/RECORD +299 -0
- dvt_core-0.59.0a51.dist-info/WHEEL +5 -0
- dvt_core-0.59.0a51.dist-info/entry_points.txt +2 -0
- 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
|