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/target_sync.py
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Target Sync Task
|
|
3
|
+
|
|
4
|
+
Handles DVT target synchronization:
|
|
5
|
+
- Scans profiles.yml connections to detect required adapter types
|
|
6
|
+
- Reports connections found and their types
|
|
7
|
+
- Shows adapter install instructions (manual pip/uv install)
|
|
8
|
+
- Resolves JDBC JARs with transitive dependencies via Maven POM
|
|
9
|
+
- Downloads all JARs to project .dvt/jdbc_jars/ directory
|
|
10
|
+
- Configures spark.jars to use pre-downloaded JARs (no Spark download at runtime)
|
|
11
|
+
- Removes unused adapters and JARs (with --clean flag)
|
|
12
|
+
|
|
13
|
+
v0.5.91: Smart sync based on profiles.yml connections
|
|
14
|
+
v0.5.93: Actual JDBC JAR download to project directory
|
|
15
|
+
v0.5.94: Show install instructions instead of auto-installing adapters
|
|
16
|
+
v0.5.95: Hybrid JAR resolution - DVT downloads with transitive deps, spark.jars config
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import urllib.request
|
|
23
|
+
import xml.etree.ElementTree as ET
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
26
|
+
|
|
27
|
+
from dbt.config.compute import ComputeRegistry
|
|
28
|
+
from dbt_common.exceptions import DbtRuntimeError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_dvt_dir() -> Path:
|
|
32
|
+
"""Get the DVT configuration directory (~/.dvt/)."""
|
|
33
|
+
dvt_dir = Path.home() / ".dvt"
|
|
34
|
+
dvt_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
return dvt_dir
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Maven repository URL
|
|
39
|
+
MAVEN_REPO = "https://repo1.maven.org/maven2"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Mapping of adapter type to dbt adapter package name
|
|
43
|
+
ADAPTER_PACKAGE_MAPPING = {
|
|
44
|
+
"postgres": "dbt-postgres",
|
|
45
|
+
"snowflake": "dbt-snowflake",
|
|
46
|
+
"bigquery": "dbt-bigquery",
|
|
47
|
+
"redshift": "dbt-redshift",
|
|
48
|
+
"spark": "dbt-spark",
|
|
49
|
+
"databricks": "dbt-databricks",
|
|
50
|
+
"trino": "dbt-trino",
|
|
51
|
+
"duckdb": "dbt-duckdb",
|
|
52
|
+
"mysql": "dbt-mysql",
|
|
53
|
+
"sqlserver": "dbt-sqlserver",
|
|
54
|
+
"synapse": "dbt-synapse",
|
|
55
|
+
"fabric": "dbt-fabric",
|
|
56
|
+
"oracle": "dbt-oracle",
|
|
57
|
+
"teradata": "dbt-teradata",
|
|
58
|
+
"clickhouse": "dbt-clickhouse",
|
|
59
|
+
"greenplum": "dbt-greenplum",
|
|
60
|
+
"vertica": "dbt-vertica",
|
|
61
|
+
"sqlite": "dbt-sqlite",
|
|
62
|
+
"mariadb": "dbt-mysql", # Uses MySQL adapter
|
|
63
|
+
"exasol": "dbt-exasol",
|
|
64
|
+
"db2": "dbt-db2",
|
|
65
|
+
"athena": "dbt-athena-community",
|
|
66
|
+
"presto": "dbt-presto",
|
|
67
|
+
"hive": "dbt-hive",
|
|
68
|
+
"impala": "dbt-impala",
|
|
69
|
+
"singlestore": "dbt-singlestore",
|
|
70
|
+
"firebolt": "dbt-firebolt",
|
|
71
|
+
"starrocks": "dbt-starrocks",
|
|
72
|
+
"doris": "dbt-doris",
|
|
73
|
+
"materialize": "dbt-materialize",
|
|
74
|
+
"rockset": "dbt-rockset",
|
|
75
|
+
"questdb": "dbt-questdb",
|
|
76
|
+
"neo4j": "dbt-neo4j",
|
|
77
|
+
"timescaledb": "dbt-postgres", # Uses PostgreSQL adapter
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Mapping of adapter type to JDBC Maven coordinates (ONE JAR per adapter)
|
|
81
|
+
# These are pure JDBC drivers that Spark uses for spark.read.jdbc()
|
|
82
|
+
# NOTE: All versions verified against Maven Central as of Dec 2025
|
|
83
|
+
ADAPTER_JDBC_MAPPING = {
|
|
84
|
+
# Official dbt-labs adapters - JDBC drivers only
|
|
85
|
+
"postgres": "org.postgresql:postgresql:42.7.4",
|
|
86
|
+
"snowflake": "net.snowflake:snowflake-jdbc:3.16.1",
|
|
87
|
+
"bigquery": "com.google.cloud.bigdataoss:gcs-connector:hadoop3-2.2.22", # GCS connector for BQ
|
|
88
|
+
"redshift": "com.amazon.redshift:redshift-jdbc42:2.1.0.32",
|
|
89
|
+
"spark": "", # Native, no JDBC needed
|
|
90
|
+
"databricks": "com.databricks:databricks-jdbc:2.6.36",
|
|
91
|
+
"trino": "io.trino:trino-jdbc:443",
|
|
92
|
+
"duckdb": "org.duckdb:duckdb_jdbc:1.1.3",
|
|
93
|
+
# Community adapters - JDBC drivers only (verified on Maven)
|
|
94
|
+
"mysql": "com.mysql:mysql-connector-j:9.1.0",
|
|
95
|
+
"sqlserver": "com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11",
|
|
96
|
+
"synapse": "com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11",
|
|
97
|
+
"fabric": "com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11",
|
|
98
|
+
"oracle": "com.oracle.database.jdbc:ojdbc11:23.6.0.24.10",
|
|
99
|
+
"teradata": "com.teradata.jdbc:terajdbc:20.00.00.20",
|
|
100
|
+
"clickhouse": "com.clickhouse:clickhouse-jdbc:0.6.5",
|
|
101
|
+
"greenplum": "org.postgresql:postgresql:42.7.4", # PostgreSQL compatible
|
|
102
|
+
"vertica": "com.vertica.jdbc:vertica-jdbc:24.3.0-0",
|
|
103
|
+
"sqlite": "org.xerial:sqlite-jdbc:3.47.1.0",
|
|
104
|
+
"mariadb": "org.mariadb.jdbc:mariadb-java-client:3.4.1",
|
|
105
|
+
"exasol": "com.exasol:exasol-jdbc:24.2.0",
|
|
106
|
+
"db2": "com.ibm.db2:jcc:11.5.9.0",
|
|
107
|
+
"presto": "io.prestosql:presto-jdbc:350",
|
|
108
|
+
"hive": "org.apache.hive:hive-jdbc:3.1.3",
|
|
109
|
+
"singlestore": "com.singlestore:singlestore-jdbc-client:1.2.9",
|
|
110
|
+
"starrocks": "com.mysql:mysql-connector-j:9.1.0", # MySQL wire protocol
|
|
111
|
+
"doris": "com.mysql:mysql-connector-j:9.1.0", # MySQL wire protocol
|
|
112
|
+
"materialize": "org.postgresql:postgresql:42.7.4", # PostgreSQL wire protocol
|
|
113
|
+
"neo4j": "org.neo4j:neo4j-jdbc-driver:4.0.10",
|
|
114
|
+
"timescaledb": "org.postgresql:postgresql:42.7.4", # PostgreSQL extension
|
|
115
|
+
"questdb": "org.postgresql:postgresql:42.7.4", # PostgreSQL wire protocol
|
|
116
|
+
# Adapters without Maven JDBC drivers (require manual JAR download):
|
|
117
|
+
# athena, impala, firebolt, rockset - use respective vendor download pages
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TargetSyncTask:
|
|
122
|
+
"""Task for synchronizing adapters and JARs based on profiles.yml connections."""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
project_dir: Optional[str] = None,
|
|
127
|
+
profiles_dir: Optional[str] = None,
|
|
128
|
+
profile_name: Optional[str] = None,
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Initialize TargetSyncTask.
|
|
132
|
+
|
|
133
|
+
:param project_dir: Path to project root directory
|
|
134
|
+
:param profiles_dir: Path to profiles directory (defaults to ~/.dvt/)
|
|
135
|
+
:param profile_name: Profile name to sync (defaults to project profile)
|
|
136
|
+
"""
|
|
137
|
+
self.project_dir = project_dir or str(Path.cwd())
|
|
138
|
+
self.profiles_dir = profiles_dir or str(get_dvt_dir())
|
|
139
|
+
self.profile_name = profile_name
|
|
140
|
+
self.compute_registry = ComputeRegistry(self.project_dir)
|
|
141
|
+
|
|
142
|
+
def _get_profile_name(self) -> Optional[str]:
|
|
143
|
+
"""Get the profile name from project or explicit parameter."""
|
|
144
|
+
if self.profile_name:
|
|
145
|
+
return self.profile_name
|
|
146
|
+
|
|
147
|
+
# Try to read from dvt_project.yml first (DVT), then dbt_project.yml (legacy)
|
|
148
|
+
project_files = [
|
|
149
|
+
Path(self.project_dir) / "dvt_project.yml",
|
|
150
|
+
Path(self.project_dir) / "dbt_project.yml",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
for project_file in project_files:
|
|
154
|
+
if project_file.exists():
|
|
155
|
+
try:
|
|
156
|
+
from dbt.clients.yaml_helper import load_yaml_text
|
|
157
|
+
|
|
158
|
+
content = project_file.read_text()
|
|
159
|
+
data = load_yaml_text(content)
|
|
160
|
+
if data and "profile" in data:
|
|
161
|
+
return data["profile"]
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def _load_profiles(self) -> Dict:
|
|
168
|
+
"""Load profiles.yml and return the data."""
|
|
169
|
+
profiles_path = Path(self.profiles_dir) / "profiles.yml"
|
|
170
|
+
if not profiles_path.exists():
|
|
171
|
+
raise DbtRuntimeError(
|
|
172
|
+
f"profiles.yml not found at {profiles_path}\n"
|
|
173
|
+
f"Create it with: dvt init <project_name>"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
from dbt.clients.yaml_helper import load_yaml_text
|
|
178
|
+
|
|
179
|
+
content = profiles_path.read_text()
|
|
180
|
+
return load_yaml_text(content) or {}
|
|
181
|
+
except Exception as e:
|
|
182
|
+
raise DbtRuntimeError(f"Failed to load profiles.yml: {e}") from e
|
|
183
|
+
|
|
184
|
+
def get_connections_info(self) -> Dict[str, Dict]:
|
|
185
|
+
"""
|
|
186
|
+
Scan profiles.yml and return detailed info about connections.
|
|
187
|
+
|
|
188
|
+
:returns: Dict mapping connection name to {type, profile}
|
|
189
|
+
"""
|
|
190
|
+
profiles = self._load_profiles()
|
|
191
|
+
connections = {}
|
|
192
|
+
|
|
193
|
+
profile_name = self._get_profile_name()
|
|
194
|
+
|
|
195
|
+
if profile_name and profile_name in profiles:
|
|
196
|
+
# Scan only the specified profile
|
|
197
|
+
profile_data = profiles[profile_name]
|
|
198
|
+
outputs = profile_data.get("outputs", {})
|
|
199
|
+
for target_name, target_config in outputs.items():
|
|
200
|
+
adapter_type = target_config.get("type")
|
|
201
|
+
if adapter_type:
|
|
202
|
+
connections[target_name] = {
|
|
203
|
+
"type": adapter_type,
|
|
204
|
+
"profile": profile_name,
|
|
205
|
+
}
|
|
206
|
+
elif profile_name:
|
|
207
|
+
# v0.59.0a18: Profile specified but not found - don't fall back to all profiles
|
|
208
|
+
raise DbtRuntimeError(
|
|
209
|
+
f"Profile '{profile_name}' not found in profiles.yml.\n"
|
|
210
|
+
f"Available profiles: {list(profiles.keys())}\n"
|
|
211
|
+
f"Create it with: dvt init"
|
|
212
|
+
)
|
|
213
|
+
# v0.59.0a18: No profile detected (not in a project) - return empty
|
|
214
|
+
# Don't scan all profiles as that breaks multi-project setups
|
|
215
|
+
|
|
216
|
+
return connections
|
|
217
|
+
|
|
218
|
+
def get_required_adapter_types(self) -> Set[str]:
|
|
219
|
+
"""
|
|
220
|
+
Scan profiles.yml and return the set of adapter types needed.
|
|
221
|
+
|
|
222
|
+
:returns: Set of adapter type names (e.g., {'postgres', 'snowflake'})
|
|
223
|
+
"""
|
|
224
|
+
connections = self.get_connections_info()
|
|
225
|
+
return {info["type"] for info in connections.values()}
|
|
226
|
+
|
|
227
|
+
def get_installed_adapters(self) -> Set[str]:
|
|
228
|
+
"""
|
|
229
|
+
Detect which dbt adapters are currently installed.
|
|
230
|
+
|
|
231
|
+
:returns: Set of installed adapter type names
|
|
232
|
+
"""
|
|
233
|
+
import importlib.util
|
|
234
|
+
|
|
235
|
+
installed = set()
|
|
236
|
+
|
|
237
|
+
adapter_modules = {
|
|
238
|
+
"postgres": "dbt.adapters.postgres",
|
|
239
|
+
"snowflake": "dbt.adapters.snowflake",
|
|
240
|
+
"bigquery": "dbt.adapters.bigquery",
|
|
241
|
+
"redshift": "dbt.adapters.redshift",
|
|
242
|
+
"spark": "dbt.adapters.spark",
|
|
243
|
+
"databricks": "dbt.adapters.databricks",
|
|
244
|
+
"trino": "dbt.adapters.trino",
|
|
245
|
+
"duckdb": "dbt.adapters.duckdb",
|
|
246
|
+
"mysql": "dbt.adapters.mysql",
|
|
247
|
+
"sqlserver": "dbt.adapters.sqlserver",
|
|
248
|
+
"synapse": "dbt.adapters.synapse",
|
|
249
|
+
"fabric": "dbt.adapters.fabric",
|
|
250
|
+
"oracle": "dbt.adapters.oracle",
|
|
251
|
+
"teradata": "dbt.adapters.teradata",
|
|
252
|
+
"clickhouse": "dbt.adapters.clickhouse",
|
|
253
|
+
"greenplum": "dbt.adapters.greenplum",
|
|
254
|
+
"vertica": "dbt.adapters.vertica",
|
|
255
|
+
"sqlite": "dbt.adapters.sqlite",
|
|
256
|
+
"mariadb": "dbt.adapters.mysql", # Uses MySQL adapter
|
|
257
|
+
"exasol": "dbt.adapters.exasol",
|
|
258
|
+
"athena": "dbt.adapters.athena",
|
|
259
|
+
"hive": "dbt.adapters.hive",
|
|
260
|
+
"impala": "dbt.adapters.impala",
|
|
261
|
+
"singlestore": "dbt.adapters.singlestore",
|
|
262
|
+
"firebolt": "dbt.adapters.firebolt",
|
|
263
|
+
"starrocks": "dbt.adapters.starrocks",
|
|
264
|
+
"doris": "dbt.adapters.doris",
|
|
265
|
+
"materialize": "dbt.adapters.materialize",
|
|
266
|
+
"rockset": "dbt.adapters.rockset",
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for adapter_type, module_name in adapter_modules.items():
|
|
270
|
+
spec = importlib.util.find_spec(module_name)
|
|
271
|
+
if spec is not None:
|
|
272
|
+
installed.add(adapter_type)
|
|
273
|
+
|
|
274
|
+
return installed
|
|
275
|
+
|
|
276
|
+
def install_adapters(
|
|
277
|
+
self, adapter_types: Set[str], verbose: bool = True
|
|
278
|
+
) -> Tuple[List[str], List[str]]:
|
|
279
|
+
"""
|
|
280
|
+
Install dbt adapters for the given adapter types.
|
|
281
|
+
|
|
282
|
+
:param adapter_types: Set of adapter type names to install
|
|
283
|
+
:param verbose: Print progress messages
|
|
284
|
+
:returns: Tuple of (installed packages, failed packages)
|
|
285
|
+
"""
|
|
286
|
+
installed = []
|
|
287
|
+
failed = []
|
|
288
|
+
|
|
289
|
+
for adapter_type in adapter_types:
|
|
290
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type)
|
|
291
|
+
if not package:
|
|
292
|
+
if verbose:
|
|
293
|
+
print(f" ⚠ Unknown adapter type: {adapter_type}")
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
if verbose:
|
|
297
|
+
print(f" Installing {package}...")
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
# Use pip to install the adapter
|
|
301
|
+
result = subprocess.run(
|
|
302
|
+
[sys.executable, "-m", "pip", "install", package, "--quiet"],
|
|
303
|
+
capture_output=True,
|
|
304
|
+
text=True,
|
|
305
|
+
)
|
|
306
|
+
if result.returncode == 0:
|
|
307
|
+
installed.append(package)
|
|
308
|
+
if verbose:
|
|
309
|
+
print(f" ✓ {package} installed")
|
|
310
|
+
else:
|
|
311
|
+
failed.append(package)
|
|
312
|
+
if verbose:
|
|
313
|
+
print(f" ✗ Failed to install {package}")
|
|
314
|
+
if result.stderr:
|
|
315
|
+
print(f" {result.stderr[:200]}")
|
|
316
|
+
except Exception as e:
|
|
317
|
+
failed.append(package)
|
|
318
|
+
if verbose:
|
|
319
|
+
print(f" ✗ Error installing {package}: {e}")
|
|
320
|
+
|
|
321
|
+
# DVT v0.59.0a42: Clean up dbt-core if it was pulled in as transitive dependency
|
|
322
|
+
if installed:
|
|
323
|
+
self._cleanup_dbt_core_conflict(verbose)
|
|
324
|
+
|
|
325
|
+
return installed, failed
|
|
326
|
+
|
|
327
|
+
def _cleanup_dbt_core_conflict(self, verbose: bool = True) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Reinstall dvt-core to restore files overwritten by dbt-core.
|
|
330
|
+
|
|
331
|
+
dbt adapters (dbt-databricks, dbt-snowflake, etc.) depend on dbt-core.
|
|
332
|
+
When installed, dbt-core overwrites dvt-core's files in the shared 'dbt'
|
|
333
|
+
namespace. This method reinstalls dvt-core to restore its files.
|
|
334
|
+
|
|
335
|
+
Note: We keep dbt-core installed because adapters need its metadata
|
|
336
|
+
(e.g., dbt-databricks calls metadata.version("dbt-core")).
|
|
337
|
+
|
|
338
|
+
DVT v0.59.0a42: Automatic cleanup after adapter installation.
|
|
339
|
+
"""
|
|
340
|
+
try:
|
|
341
|
+
# Check if dbt-core is installed
|
|
342
|
+
result = subprocess.run(
|
|
343
|
+
[sys.executable, "-m", "pip", "show", "dbt-core"],
|
|
344
|
+
capture_output=True,
|
|
345
|
+
text=True,
|
|
346
|
+
)
|
|
347
|
+
if result.returncode != 0:
|
|
348
|
+
# dbt-core not installed, nothing to clean up
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
if verbose:
|
|
352
|
+
print("\n 🔧 Restoring dvt-core files...")
|
|
353
|
+
print(" (dbt adapters install dbt-core which overwrites dvt-core files)")
|
|
354
|
+
|
|
355
|
+
# Reinstall dvt-core to restore overwritten files
|
|
356
|
+
# Keep dbt-core installed - adapters need its metadata
|
|
357
|
+
subprocess.run(
|
|
358
|
+
[sys.executable, "-m", "pip", "install", "--reinstall", "--no-deps",
|
|
359
|
+
"dvt-core", "--quiet"],
|
|
360
|
+
capture_output=True,
|
|
361
|
+
text=True,
|
|
362
|
+
)
|
|
363
|
+
if verbose:
|
|
364
|
+
print(" ✓ Restored dvt-core files")
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
if verbose:
|
|
368
|
+
print(f" ⚠ Cleanup warning: {e}")
|
|
369
|
+
|
|
370
|
+
def uninstall_adapters(
|
|
371
|
+
self, adapter_types: Set[str], verbose: bool = True
|
|
372
|
+
) -> Tuple[List[str], List[str]]:
|
|
373
|
+
"""
|
|
374
|
+
Uninstall dbt adapters for the given adapter types.
|
|
375
|
+
|
|
376
|
+
:param adapter_types: Set of adapter type names to uninstall
|
|
377
|
+
:param verbose: Print progress messages
|
|
378
|
+
:returns: Tuple of (uninstalled packages, failed packages)
|
|
379
|
+
"""
|
|
380
|
+
uninstalled = []
|
|
381
|
+
failed = []
|
|
382
|
+
|
|
383
|
+
for adapter_type in adapter_types:
|
|
384
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type)
|
|
385
|
+
if not package:
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
if verbose:
|
|
389
|
+
print(f" Removing {package}...")
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
result = subprocess.run(
|
|
393
|
+
[sys.executable, "-m", "pip", "uninstall", package, "-y", "--quiet"],
|
|
394
|
+
capture_output=True,
|
|
395
|
+
text=True,
|
|
396
|
+
)
|
|
397
|
+
if result.returncode == 0:
|
|
398
|
+
uninstalled.append(package)
|
|
399
|
+
if verbose:
|
|
400
|
+
print(f" ✓ {package} removed")
|
|
401
|
+
else:
|
|
402
|
+
failed.append(package)
|
|
403
|
+
if verbose:
|
|
404
|
+
print(f" ✗ Failed to remove {package}")
|
|
405
|
+
except Exception as e:
|
|
406
|
+
failed.append(package)
|
|
407
|
+
if verbose:
|
|
408
|
+
print(f" ✗ Error removing {package}: {e}")
|
|
409
|
+
|
|
410
|
+
return uninstalled, failed
|
|
411
|
+
|
|
412
|
+
def _maven_coord_to_url(self, coord: str) -> Tuple[str, str]:
|
|
413
|
+
"""
|
|
414
|
+
Convert Maven coordinate to download URL and JAR filename.
|
|
415
|
+
|
|
416
|
+
:param coord: Maven coordinate (e.g., 'org.postgresql:postgresql:42.7.4')
|
|
417
|
+
:returns: Tuple of (download_url, jar_filename)
|
|
418
|
+
"""
|
|
419
|
+
parts = coord.split(":")
|
|
420
|
+
if len(parts) < 3:
|
|
421
|
+
raise ValueError(f"Invalid Maven coordinate: {coord}")
|
|
422
|
+
|
|
423
|
+
group_id = parts[0]
|
|
424
|
+
artifact_id = parts[1]
|
|
425
|
+
version = parts[2]
|
|
426
|
+
|
|
427
|
+
# Convert group.id to group/id path
|
|
428
|
+
group_path = group_id.replace(".", "/")
|
|
429
|
+
|
|
430
|
+
# Build URL
|
|
431
|
+
jar_name = f"{artifact_id}-{version}.jar"
|
|
432
|
+
url = f"{MAVEN_REPO}/{group_path}/{artifact_id}/{version}/{jar_name}"
|
|
433
|
+
|
|
434
|
+
return url, jar_name
|
|
435
|
+
|
|
436
|
+
def _resolve_transitive_deps(
|
|
437
|
+
self, coord: str, resolved: Optional[Set[str]] = None, depth: int = 0
|
|
438
|
+
) -> Set[str]:
|
|
439
|
+
"""
|
|
440
|
+
Resolve transitive dependencies for a Maven coordinate by parsing POM file.
|
|
441
|
+
|
|
442
|
+
:param coord: Maven coordinate (e.g., 'org.postgresql:postgresql:42.7.4')
|
|
443
|
+
:param resolved: Set of already resolved coordinates (to avoid cycles)
|
|
444
|
+
:param depth: Current recursion depth (max 3 to avoid deep trees)
|
|
445
|
+
:returns: Set of all coordinates (including transitive deps)
|
|
446
|
+
"""
|
|
447
|
+
if resolved is None:
|
|
448
|
+
resolved = set()
|
|
449
|
+
|
|
450
|
+
# Parse coordinate
|
|
451
|
+
parts = coord.split(":")
|
|
452
|
+
if len(parts) < 3:
|
|
453
|
+
return resolved
|
|
454
|
+
|
|
455
|
+
group_id, artifact_id, version = parts[0], parts[1], parts[2]
|
|
456
|
+
|
|
457
|
+
# Avoid cycles and limit depth
|
|
458
|
+
if coord in resolved or depth > 3:
|
|
459
|
+
return resolved
|
|
460
|
+
|
|
461
|
+
resolved.add(coord)
|
|
462
|
+
|
|
463
|
+
# Skip transitive resolution for known self-contained JDBC drivers
|
|
464
|
+
# Most JDBC drivers bundle their dependencies or have minimal deps
|
|
465
|
+
self_contained_drivers = {
|
|
466
|
+
"postgresql", "snowflake-jdbc", "mysql-connector-j", "mssql-jdbc",
|
|
467
|
+
"ojdbc11", "terajdbc", "clickhouse-jdbc", "sqlite-jdbc",
|
|
468
|
+
"mariadb-java-client", "exasol-jdbc", "jcc", "trino-jdbc",
|
|
469
|
+
"presto-jdbc", "duckdb_jdbc", "databricks-jdbc", "redshift-jdbc42",
|
|
470
|
+
"singlestore-jdbc-client", "neo4j-jdbc-driver", "vertica-jdbc"
|
|
471
|
+
}
|
|
472
|
+
if artifact_id in self_contained_drivers:
|
|
473
|
+
return resolved
|
|
474
|
+
|
|
475
|
+
# Try to fetch and parse POM for transitive deps
|
|
476
|
+
try:
|
|
477
|
+
group_path = group_id.replace(".", "/")
|
|
478
|
+
pom_url = f"{MAVEN_REPO}/{group_path}/{artifact_id}/{version}/{artifact_id}-{version}.pom"
|
|
479
|
+
|
|
480
|
+
request = urllib.request.Request(
|
|
481
|
+
pom_url,
|
|
482
|
+
headers={"User-Agent": "DVT-Core/0.5.95"}
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
with urllib.request.urlopen(request, timeout=10) as response:
|
|
486
|
+
pom_content = response.read().decode("utf-8")
|
|
487
|
+
|
|
488
|
+
# Parse POM XML
|
|
489
|
+
root = ET.fromstring(pom_content)
|
|
490
|
+
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
|
|
491
|
+
|
|
492
|
+
# Find dependencies
|
|
493
|
+
for dep in root.findall(".//m:dependency", ns):
|
|
494
|
+
dep_group = dep.find("m:groupId", ns)
|
|
495
|
+
dep_artifact = dep.find("m:artifactId", ns)
|
|
496
|
+
dep_version = dep.find("m:version", ns)
|
|
497
|
+
dep_scope = dep.find("m:scope", ns)
|
|
498
|
+
dep_optional = dep.find("m:optional", ns)
|
|
499
|
+
|
|
500
|
+
# Skip test, provided, and optional dependencies
|
|
501
|
+
if dep_scope is not None and dep_scope.text in ("test", "provided"):
|
|
502
|
+
continue
|
|
503
|
+
if dep_optional is not None and dep_optional.text == "true":
|
|
504
|
+
continue
|
|
505
|
+
|
|
506
|
+
if dep_group is not None and dep_artifact is not None and dep_version is not None:
|
|
507
|
+
dep_coord = f"{dep_group.text}:{dep_artifact.text}:{dep_version.text}"
|
|
508
|
+
# Recursively resolve (limited depth)
|
|
509
|
+
self._resolve_transitive_deps(dep_coord, resolved, depth + 1)
|
|
510
|
+
|
|
511
|
+
except Exception:
|
|
512
|
+
# If POM parsing fails, just return current resolved set
|
|
513
|
+
pass
|
|
514
|
+
|
|
515
|
+
return resolved
|
|
516
|
+
|
|
517
|
+
def _download_jar(self, url: str, dest_path: Path, verbose: bool = True) -> bool:
|
|
518
|
+
"""
|
|
519
|
+
Download a JAR file from URL.
|
|
520
|
+
|
|
521
|
+
:param url: URL to download from
|
|
522
|
+
:param dest_path: Destination path
|
|
523
|
+
:param verbose: Print progress messages
|
|
524
|
+
:returns: True if successful
|
|
525
|
+
"""
|
|
526
|
+
try:
|
|
527
|
+
if verbose:
|
|
528
|
+
print(f" Downloading {dest_path.name}...")
|
|
529
|
+
|
|
530
|
+
# Create request with user agent
|
|
531
|
+
request = urllib.request.Request(
|
|
532
|
+
url,
|
|
533
|
+
headers={"User-Agent": "DVT-Core/0.5.95"}
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
with urllib.request.urlopen(request, timeout=60) as response:
|
|
537
|
+
with open(dest_path, "wb") as f:
|
|
538
|
+
f.write(response.read())
|
|
539
|
+
|
|
540
|
+
if verbose:
|
|
541
|
+
size_mb = dest_path.stat().st_size / (1024 * 1024)
|
|
542
|
+
print(f" ✓ Downloaded ({size_mb:.1f} MB)")
|
|
543
|
+
return True
|
|
544
|
+
|
|
545
|
+
except Exception as e:
|
|
546
|
+
if verbose:
|
|
547
|
+
print(f" ✗ Failed: {e}")
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
def download_jdbc_jars(
|
|
551
|
+
self, adapter_types: Set[str], verbose: bool = True
|
|
552
|
+
) -> Tuple[List[str], List[str]]:
|
|
553
|
+
"""
|
|
554
|
+
Download JDBC JARs to project .dvt/jdbc_jars/ directory.
|
|
555
|
+
|
|
556
|
+
v0.5.95: Hybrid approach - resolves transitive dependencies via Maven POM,
|
|
557
|
+
downloads all JARs to local cache, then uses spark.jars for fast startup.
|
|
558
|
+
|
|
559
|
+
:param adapter_types: Set of adapter type names
|
|
560
|
+
:param verbose: Print progress messages
|
|
561
|
+
:returns: Tuple of (downloaded jars, failed jars)
|
|
562
|
+
"""
|
|
563
|
+
# Ensure jdbc_jars directory exists
|
|
564
|
+
jdbc_jars_dir = Path(self.project_dir) / ".dvt" / "jdbc_jars"
|
|
565
|
+
jdbc_jars_dir.mkdir(parents=True, exist_ok=True)
|
|
566
|
+
|
|
567
|
+
downloaded = []
|
|
568
|
+
failed = []
|
|
569
|
+
|
|
570
|
+
# Build list of required JAR coordinates (direct dependencies)
|
|
571
|
+
direct_coords = set()
|
|
572
|
+
for adapter_type in adapter_types:
|
|
573
|
+
jars = ADAPTER_JDBC_MAPPING.get(adapter_type, "")
|
|
574
|
+
if jars:
|
|
575
|
+
for jar in jars.split(","):
|
|
576
|
+
jar = jar.strip()
|
|
577
|
+
if jar:
|
|
578
|
+
direct_coords.add(jar)
|
|
579
|
+
|
|
580
|
+
if not direct_coords:
|
|
581
|
+
if verbose:
|
|
582
|
+
print("\n No JDBC JARs needed for these adapters")
|
|
583
|
+
return downloaded, failed
|
|
584
|
+
|
|
585
|
+
if verbose:
|
|
586
|
+
print(f"\n Resolving JDBC dependencies...")
|
|
587
|
+
print(f" Direct dependencies: {len(direct_coords)}")
|
|
588
|
+
|
|
589
|
+
# Resolve transitive dependencies for all direct coords
|
|
590
|
+
all_coords = set()
|
|
591
|
+
for coord in direct_coords:
|
|
592
|
+
resolved = self._resolve_transitive_deps(coord)
|
|
593
|
+
all_coords.update(resolved)
|
|
594
|
+
|
|
595
|
+
if verbose:
|
|
596
|
+
transitive_count = len(all_coords) - len(direct_coords)
|
|
597
|
+
if transitive_count > 0:
|
|
598
|
+
print(f" Transitive dependencies: {transitive_count}")
|
|
599
|
+
print(f" Total JARs to download: {len(all_coords)}")
|
|
600
|
+
print(f"\n Downloading to {jdbc_jars_dir}/")
|
|
601
|
+
|
|
602
|
+
for coord in sorted(all_coords):
|
|
603
|
+
try:
|
|
604
|
+
url, jar_name = self._maven_coord_to_url(coord)
|
|
605
|
+
dest_path = jdbc_jars_dir / jar_name
|
|
606
|
+
|
|
607
|
+
# Skip if already downloaded
|
|
608
|
+
if dest_path.exists():
|
|
609
|
+
if verbose:
|
|
610
|
+
print(f" {jar_name} (cached)")
|
|
611
|
+
downloaded.append(jar_name)
|
|
612
|
+
continue
|
|
613
|
+
|
|
614
|
+
# Download the JAR
|
|
615
|
+
if self._download_jar(url, dest_path, verbose):
|
|
616
|
+
downloaded.append(jar_name)
|
|
617
|
+
else:
|
|
618
|
+
failed.append(jar_name)
|
|
619
|
+
|
|
620
|
+
except ValueError as e:
|
|
621
|
+
if verbose:
|
|
622
|
+
print(f" ⚠ Skipping {coord}: {e}")
|
|
623
|
+
continue
|
|
624
|
+
except Exception as e:
|
|
625
|
+
if verbose:
|
|
626
|
+
print(f" ✗ Error with {coord}: {e}")
|
|
627
|
+
failed.append(coord)
|
|
628
|
+
|
|
629
|
+
return downloaded, failed
|
|
630
|
+
|
|
631
|
+
def update_jdbc_jars(self, adapter_types: Set[str], verbose: bool = True) -> bool:
|
|
632
|
+
"""
|
|
633
|
+
Report JDBC JAR status (JARs discovered at runtime by Spark).
|
|
634
|
+
|
|
635
|
+
v0.5.96: No longer stores spark.jars in config - JARs are discovered at runtime.
|
|
636
|
+
This enables project folder portability (move folder → JARs still work).
|
|
637
|
+
|
|
638
|
+
The LocalStrategy._get_jdbc_jars() method discovers JARs from current project
|
|
639
|
+
directory at runtime: <project>/.dvt/jdbc_jars/*.jar
|
|
640
|
+
|
|
641
|
+
:param adapter_types: Set of adapter type names
|
|
642
|
+
:param verbose: Print progress messages
|
|
643
|
+
:returns: True if JARs found
|
|
644
|
+
"""
|
|
645
|
+
# Get the jdbc_jars directory
|
|
646
|
+
jdbc_jars_dir = Path(self.project_dir) / ".dvt" / "jdbc_jars"
|
|
647
|
+
|
|
648
|
+
# Find all downloaded JAR files
|
|
649
|
+
jar_paths = []
|
|
650
|
+
if jdbc_jars_dir.exists():
|
|
651
|
+
jar_paths = sorted(jdbc_jars_dir.glob("*.jar"))
|
|
652
|
+
|
|
653
|
+
if verbose:
|
|
654
|
+
if jar_paths:
|
|
655
|
+
print(f"\n JDBC JARs downloaded ({len(jar_paths)}):")
|
|
656
|
+
for jar_path in jar_paths:
|
|
657
|
+
print(f" - {jar_path.name}")
|
|
658
|
+
print(f"\n ✓ JARs stored in: {jdbc_jars_dir}")
|
|
659
|
+
print(" (Spark discovers JARs at runtime - portable across folder moves)")
|
|
660
|
+
else:
|
|
661
|
+
print("\n No JDBC JARs downloaded")
|
|
662
|
+
|
|
663
|
+
# v0.5.96: Remove spark.jars from config if present (old absolute path config)
|
|
664
|
+
# JARs are now discovered at runtime from project directory
|
|
665
|
+
spark_local = self.compute_registry.get("spark-local")
|
|
666
|
+
if spark_local:
|
|
667
|
+
modified = False
|
|
668
|
+
if "spark.jars" in spark_local.config:
|
|
669
|
+
spark_local.config.pop("spark.jars", None)
|
|
670
|
+
modified = True
|
|
671
|
+
if "spark.jars.packages" in spark_local.config:
|
|
672
|
+
spark_local.config.pop("spark.jars.packages", None)
|
|
673
|
+
modified = True
|
|
674
|
+
if modified:
|
|
675
|
+
self.compute_registry._save()
|
|
676
|
+
if verbose:
|
|
677
|
+
print("\n ✓ Cleaned up old spark.jars config (now uses runtime discovery)")
|
|
678
|
+
|
|
679
|
+
return bool(jar_paths)
|
|
680
|
+
|
|
681
|
+
def sync(self, verbose: bool = True, clean: bool = False, dry_run: bool = False) -> bool:
|
|
682
|
+
"""
|
|
683
|
+
Synchronize adapters and JARs based on profiles.yml.
|
|
684
|
+
|
|
685
|
+
:param verbose: Print progress messages
|
|
686
|
+
:param clean: If True, remove adapters not needed by profiles.yml
|
|
687
|
+
:param dry_run: If True, only report what would be done without making changes
|
|
688
|
+
:returns: True if sync successful
|
|
689
|
+
"""
|
|
690
|
+
if verbose:
|
|
691
|
+
print("\nDVT Target Sync")
|
|
692
|
+
print("=" * 60)
|
|
693
|
+
|
|
694
|
+
# Get connection info from profiles.yml
|
|
695
|
+
try:
|
|
696
|
+
connections = self.get_connections_info()
|
|
697
|
+
required = self.get_required_adapter_types()
|
|
698
|
+
except DbtRuntimeError as e:
|
|
699
|
+
print(f"✗ Error: {e}")
|
|
700
|
+
return False
|
|
701
|
+
|
|
702
|
+
# Report connections found
|
|
703
|
+
if verbose:
|
|
704
|
+
profile_name = self._get_profile_name()
|
|
705
|
+
if profile_name:
|
|
706
|
+
print(f"Profile: {profile_name}")
|
|
707
|
+
print(f"\nConnections found: {len(connections)}")
|
|
708
|
+
print("-" * 40)
|
|
709
|
+
|
|
710
|
+
# Group connections by type
|
|
711
|
+
by_type: Dict[str, List[str]] = {}
|
|
712
|
+
for conn_name, info in connections.items():
|
|
713
|
+
adapter_type = info["type"]
|
|
714
|
+
if adapter_type not in by_type:
|
|
715
|
+
by_type[adapter_type] = []
|
|
716
|
+
by_type[adapter_type].append(conn_name)
|
|
717
|
+
|
|
718
|
+
for adapter_type in sorted(by_type.keys()):
|
|
719
|
+
conn_names = by_type[adapter_type]
|
|
720
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
721
|
+
print(f"\n {adapter_type} ({len(conn_names)} connection(s)):")
|
|
722
|
+
for conn_name in conn_names:
|
|
723
|
+
print(f" - {conn_name}")
|
|
724
|
+
print(f" Package: {package}")
|
|
725
|
+
|
|
726
|
+
if not required:
|
|
727
|
+
print("\n⚠ No connections found in profiles.yml")
|
|
728
|
+
print(" Add connections to ~/.dvt/profiles.yml first")
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
# Get currently installed adapters
|
|
732
|
+
installed = self.get_installed_adapters()
|
|
733
|
+
|
|
734
|
+
# Determine what to install and uninstall
|
|
735
|
+
to_install = required - installed
|
|
736
|
+
to_uninstall = installed - required if clean else set()
|
|
737
|
+
|
|
738
|
+
# Report what will be installed
|
|
739
|
+
if verbose:
|
|
740
|
+
print("\n" + "-" * 40)
|
|
741
|
+
print("\nAdapter Status:")
|
|
742
|
+
|
|
743
|
+
if to_install:
|
|
744
|
+
print(f"\n To install ({len(to_install)}):")
|
|
745
|
+
for adapter_type in sorted(to_install):
|
|
746
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
747
|
+
print(f" - {adapter_type}: pip install {package}")
|
|
748
|
+
else:
|
|
749
|
+
print("\n ✓ All required adapters already installed")
|
|
750
|
+
|
|
751
|
+
if to_uninstall:
|
|
752
|
+
print(f"\n To remove ({len(to_uninstall)}):")
|
|
753
|
+
for adapter_type in sorted(to_uninstall):
|
|
754
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
755
|
+
print(f" - {adapter_type}: pip uninstall {package}")
|
|
756
|
+
|
|
757
|
+
# Report adapters installed but not used (if not cleaning)
|
|
758
|
+
unused = installed - required
|
|
759
|
+
if unused and not clean:
|
|
760
|
+
print(f"\n Installed but not used ({len(unused)}):")
|
|
761
|
+
for adapter_type in sorted(unused):
|
|
762
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
763
|
+
print(f" - {adapter_type} ({package})")
|
|
764
|
+
print(" (use --clean to remove unused adapters)")
|
|
765
|
+
|
|
766
|
+
# If dry run, stop here
|
|
767
|
+
if dry_run:
|
|
768
|
+
if verbose:
|
|
769
|
+
print("\n" + "=" * 60)
|
|
770
|
+
print("Dry run complete. No changes made.")
|
|
771
|
+
return True
|
|
772
|
+
|
|
773
|
+
# Show install instructions for missing adapters (don't auto-install)
|
|
774
|
+
if to_install:
|
|
775
|
+
if verbose:
|
|
776
|
+
print(f"\n" + "-" * 40)
|
|
777
|
+
print(f"\nMissing Adapters ({len(to_install)}):")
|
|
778
|
+
print(" Install manually with pip or uv:\n")
|
|
779
|
+
for adapter_type in sorted(to_install):
|
|
780
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
781
|
+
print(f" pip install {package}")
|
|
782
|
+
print(f" # or: uv pip install {package}\n")
|
|
783
|
+
|
|
784
|
+
# Show uninstall instructions for unused adapters (only if clean=True)
|
|
785
|
+
if to_uninstall:
|
|
786
|
+
if verbose:
|
|
787
|
+
print(f"\n" + "-" * 40)
|
|
788
|
+
print(f"\nUnused Adapters ({len(to_uninstall)}):")
|
|
789
|
+
print(" Uninstall manually with pip or uv:\n")
|
|
790
|
+
for adapter_type in sorted(to_uninstall):
|
|
791
|
+
package = ADAPTER_PACKAGE_MAPPING.get(adapter_type, "unknown")
|
|
792
|
+
print(f" pip uninstall {package}")
|
|
793
|
+
print(f" # or: uv pip uninstall {package}\n")
|
|
794
|
+
|
|
795
|
+
# Download JDBC JARs to project directory
|
|
796
|
+
if verbose:
|
|
797
|
+
print("\n" + "-" * 40)
|
|
798
|
+
print("\nDownloading JDBC JARs...")
|
|
799
|
+
downloaded_jars, failed_jars = self.download_jdbc_jars(required, verbose)
|
|
800
|
+
if failed_jars and verbose:
|
|
801
|
+
print(f"\n ⚠ {len(failed_jars)} JAR(s) failed to download")
|
|
802
|
+
|
|
803
|
+
# Update JDBC JARs config in spark-local
|
|
804
|
+
if verbose:
|
|
805
|
+
print("\n" + "-" * 40)
|
|
806
|
+
print("\nUpdating JDBC configuration...")
|
|
807
|
+
self.update_jdbc_jars(required, verbose)
|
|
808
|
+
|
|
809
|
+
if verbose:
|
|
810
|
+
print("\n" + "=" * 60)
|
|
811
|
+
print("✓ Sync complete")
|
|
812
|
+
print("\nYou can now run: dvt run")
|
|
813
|
+
|
|
814
|
+
return True
|