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
@@ -0,0 +1,408 @@
1
+ """
2
+ DVT (Data Virtualization Tool) Profile Extensions
3
+
4
+ This module extends the dbt Profile class to support multiple named connections
5
+ per profile, enabling multi-source data federation.
6
+ """
7
+
8
+
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Optional, Tuple
12
+
13
+ from dbt.adapters.contracts.connection import Credentials
14
+ from dbt.config.profile import Profile
15
+ from dbt.exceptions import DbtProfileError, DbtRuntimeError
16
+ from dbt_common.dataclass_schema import ValidationError
17
+
18
+
19
+ @dataclass
20
+ class DVTProfile(Profile):
21
+ """
22
+ Extended Profile class that supports multiple named connections.
23
+
24
+ In addition to the standard dbt Profile fields (profile_name, target_name, threads, credentials),
25
+ DVTProfile adds:
26
+ - connections: Dict of named connections (each with its own Credentials)
27
+ - default_target: The connection name to use for materialization by default
28
+
29
+ The 'credentials' field is maintained for backward compatibility and points to the default_target credentials.
30
+ """
31
+
32
+ # Additional DVT-specific fields
33
+ outputs: Dict[str, Credentials] = field(default_factory=dict)
34
+ default_target: Optional[str] = None
35
+
36
+ def __init__(
37
+ self,
38
+ profile_name: str,
39
+ target_name: str,
40
+ threads: int,
41
+ credentials: Credentials,
42
+ connections: Optional[Dict[str, Credentials]] = None,
43
+ default_target: Optional[str] = None,
44
+ ) -> None:
45
+ """
46
+ Initialize DVT Profile with multi-connection support.
47
+
48
+ :param profile_name: Name of the profile
49
+ :param target_name: Name of the target (for backward compatibility)
50
+ :param threads: Number of threads for parallel execution
51
+ :param credentials: Default credentials (for backward compatibility)
52
+ :param connections: Dictionary of named outputs (same as dbt)
53
+ :param default_target: Default output name for materialization
54
+ """
55
+ super().__init__(
56
+ profile_name=profile_name,
57
+ target_name=target_name,
58
+ threads=threads,
59
+ credentials=credentials,
60
+ )
61
+
62
+ self.outputs = connections or {}
63
+ self.default_target = default_target
64
+
65
+ # If outputs dict is provided but default_target is not, use target_name
66
+ if self.outputs and not self.default_target:
67
+ self.default_target = target_name
68
+
69
+ # Ensure backward compatibility: if outputs is empty, populate with single credentials
70
+ if not self.outputs and credentials:
71
+ self.outputs[target_name] = credentials
72
+ self.default_target = target_name
73
+
74
+ def get_connection(self, connection_name: Optional[str] = None) -> Credentials:
75
+ """
76
+ Get credentials for a specific output.
77
+
78
+ :param connection_name: Name of the output. If None, returns default_target credentials.
79
+ :returns: Credentials for the specified output
80
+ :raises DbtProfileError: If connection_name not found
81
+ """
82
+ name = connection_name or self.default_target
83
+
84
+ if not name:
85
+ raise DbtProfileError("No output name specified and no default_target configured")
86
+
87
+ if name not in self.outputs:
88
+ available = ", ".join(self.outputs.keys())
89
+ raise DbtProfileError(
90
+ f"Output '{name}' not found in profile '{self.profile_name}'. "
91
+ f"Available outputs: {available}"
92
+ )
93
+
94
+ return self.outputs[name]
95
+
96
+ def to_profile_info(self, serialize_credentials: bool = False) -> Dict[str, Any]:
97
+ """
98
+ Serialize DVT profile to dict.
99
+
100
+ :param serialize_credentials: If True, serialize all credentials to dicts
101
+ :returns: Serialized profile dict
102
+ """
103
+ result = super().to_profile_info(serialize_credentials=serialize_credentials)
104
+
105
+ # DVT uses standard dbt format - no need to serialize outputs separately
106
+ # since multi-output support is just for convenience, validation uses single target
107
+
108
+ return result
109
+
110
+ @staticmethod
111
+ def _parse_connections_from_profile(
112
+ profile: Dict[str, Any], profile_name: str
113
+ ) -> Tuple[Dict[str, Credentials], Optional[str]]:
114
+ """
115
+ Parse multiple outputs from profile configuration.
116
+
117
+ DVT uses standard dbt format with 'outputs' key and optional multi-output support:
118
+ outputs:
119
+ postgres_prod:
120
+ type: postgres
121
+ host: prod.example.com
122
+ ...
123
+ snowflake_warehouse:
124
+ type: snowflake
125
+ account: abc123
126
+ ...
127
+ target: postgres_prod
128
+
129
+ :param profile: Raw profile dict from YAML
130
+ :param profile_name: Name of the profile
131
+ :returns: Tuple of (outputs dict, default_target name)
132
+ :raises DbtProfileError: If profile format is invalid
133
+ """
134
+ # avoid an import cycle
135
+ from dbt.adapters.factory import load_plugin
136
+
137
+ outputs = {}
138
+ default_target = None
139
+
140
+ # Check for 'outputs' key (standard dbt format)
141
+ if "outputs" in profile:
142
+ raw_outputs = profile["outputs"]
143
+
144
+ if not isinstance(raw_outputs, dict):
145
+ raise DbtProfileError(
146
+ f"'outputs' in profile '{profile_name}' must be a dictionary"
147
+ )
148
+
149
+ # Parse each output
150
+ for output_name, output_data in raw_outputs.items():
151
+ if not isinstance(output_data, dict):
152
+ raise DbtProfileError(
153
+ f"Output '{output_name}' in profile '{profile_name}' must be a dictionary"
154
+ )
155
+
156
+ if "type" not in output_data:
157
+ raise DbtProfileError(
158
+ f"Output '{output_name}' in profile '{profile_name}' missing required 'type' field"
159
+ )
160
+
161
+ # Parse credentials for this output
162
+ output_data_copy = output_data.copy()
163
+ typename = output_data_copy.pop("type")
164
+
165
+ try:
166
+ cls = load_plugin(typename)
167
+ data = cls.translate_aliases(output_data_copy)
168
+
169
+ # v0.59.0a39: Resolve DuckDB paths if they don't exist
170
+ # This handles cases where projects have been moved
171
+ if typename == "duckdb" and "path" in data:
172
+ resolved_path = DVTProfile._resolve_duckdb_path(data["path"])
173
+ if resolved_path is not None:
174
+ data["path"] = resolved_path
175
+ # Log resolution for debugging (can be removed in production)
176
+ import logging
177
+ logger = logging.getLogger(__name__)
178
+ logger.debug(
179
+ f"Resolved DuckDB path from {output_data_copy.get('path')} "
180
+ f"to {resolved_path}"
181
+ )
182
+
183
+ cls.validate(data)
184
+ credentials = cls.from_dict(data)
185
+ outputs[output_name] = credentials
186
+ except (DbtRuntimeError, ValidationError) as e:
187
+ msg = str(e) if isinstance(e, DbtRuntimeError) else e.message
188
+ raise DbtProfileError(
189
+ f"Credentials for output '{output_name}' in profile '{profile_name}' invalid: {msg}"
190
+ ) from e
191
+
192
+ # Get default target - use 'target' field (standard dbt)
193
+ # Also support 'default_target' for backward compatibility
194
+ default_target = profile.get("target") or profile.get("default_target")
195
+ if not default_target and outputs:
196
+ # If not specified, use first output as default
197
+ default_target = list(outputs.keys())[0]
198
+
199
+ if default_target and default_target not in outputs:
200
+ raise DbtProfileError(
201
+ f"target '{default_target}' not found in outputs of profile '{profile_name}'"
202
+ )
203
+
204
+ return outputs, default_target
205
+
206
+ @staticmethod
207
+ def _resolve_duckdb_path(path: str) -> Optional[str]:
208
+ """
209
+ Resolve DuckDB path if it doesn't exist.
210
+
211
+ v0.59.0a39: If the path doesn't exist, try to resolve it relative to
212
+ the current project directory. This handles cases where projects
213
+ have been moved.
214
+
215
+ Strategy:
216
+ 1. If path exists, use as-is
217
+ 2. If path doesn't exist, extract the filename (default.duckdb)
218
+ 3. Look for that file in .dvt/ directory of current working directory
219
+ 4. This assumes dvt commands are run from project root
220
+
221
+ :param path: DuckDB path from profiles.yml
222
+ :returns: Resolved path if found, None if path exists or can't be resolved
223
+ """
224
+ path_obj = Path(path)
225
+
226
+ # If path exists, no resolution needed
227
+ if path_obj.exists():
228
+ return None
229
+
230
+ # Extract filename (e.g., "default.duckdb")
231
+ filename = path_obj.name
232
+
233
+ # Try to find it in current directory's .dvt/ folder
234
+ # This assumes dvt commands are run from project root
235
+ current_dir = Path.cwd()
236
+ potential_path = current_dir / ".dvt" / filename
237
+
238
+ if potential_path.exists():
239
+ return str(potential_path.resolve())
240
+
241
+ # If still not found, try looking for any .dvt directory in current or parent dirs
242
+ # Walk up to 3 levels to find project root (where .dvt/ might be)
243
+ check_dir = current_dir
244
+ for _ in range(3):
245
+ potential_path = check_dir / ".dvt" / filename
246
+ if potential_path.exists():
247
+ return str(potential_path.resolve())
248
+ parent = check_dir.parent
249
+ if parent == check_dir: # Reached root
250
+ break
251
+ check_dir = parent
252
+
253
+ # If we can't resolve it, return None (will use original path and fail with clear error)
254
+ return None
255
+
256
+ @classmethod
257
+ def from_raw_profile_info(
258
+ cls,
259
+ raw_profile: Dict[str, Any],
260
+ profile_name: str,
261
+ renderer: Any, # ProfileRenderer
262
+ target_override: Optional[str] = None,
263
+ threads_override: Optional[int] = None,
264
+ ) -> "DVTProfile":
265
+ """
266
+ Create a DVTProfile from raw profile information.
267
+
268
+ This method supports both DVT multi-connection format and legacy dbt format.
269
+
270
+ :param raw_profile: The profile data for a single profile
271
+ :param profile_name: The profile name used
272
+ :param renderer: The config renderer
273
+ :param target_override: The target to use, if provided on the command line
274
+ :param threads_override: The thread count to use, if provided
275
+ :returns: The new DVTProfile object
276
+ :raises DbtProfileError: If the profile is invalid
277
+ """
278
+ # Check if this is a DVT multi-output profile
279
+ if "outputs" in raw_profile and len(raw_profile.get("outputs", {})) > 0:
280
+ # Parse outputs
281
+ outputs, default_target = cls._parse_connections_from_profile(
282
+ raw_profile, profile_name
283
+ )
284
+
285
+ if not outputs:
286
+ raise DbtProfileError(
287
+ f"Profile '{profile_name}' has 'outputs' key but no valid outputs defined"
288
+ )
289
+
290
+ # Get threads
291
+ threads = raw_profile.get("threads", 1)
292
+ if threads_override is not None:
293
+ threads = threads_override
294
+
295
+ # Determine target name
296
+ if target_override:
297
+ target_name = target_override
298
+ if target_name not in outputs:
299
+ raise DbtProfileError(
300
+ f"Target override '{target_name}' not found in outputs of profile '{profile_name}'"
301
+ )
302
+ default_target = target_name
303
+ else:
304
+ target_name = default_target or list(outputs.keys())[0]
305
+
306
+ # Get default credentials for backward compatibility
307
+ credentials = outputs[target_name]
308
+
309
+ profile = cls(
310
+ profile_name=profile_name,
311
+ target_name=target_name,
312
+ threads=threads,
313
+ credentials=credentials,
314
+ connections=outputs,
315
+ default_target=default_target,
316
+ )
317
+ profile.validate()
318
+ return profile
319
+
320
+ else:
321
+ # Fall back to legacy dbt format - use parent class implementation
322
+ legacy_profile = Profile.from_raw_profile_info(
323
+ raw_profile=raw_profile,
324
+ profile_name=profile_name,
325
+ renderer=renderer,
326
+ target_override=target_override,
327
+ threads_override=threads_override,
328
+ )
329
+
330
+ # Convert to DVTProfile for consistency
331
+ return cls(
332
+ profile_name=legacy_profile.profile_name,
333
+ target_name=legacy_profile.target_name,
334
+ threads=legacy_profile.threads,
335
+ credentials=legacy_profile.credentials,
336
+ connections={legacy_profile.target_name: legacy_profile.credentials},
337
+ default_target=legacy_profile.target_name,
338
+ )
339
+
340
+ @classmethod
341
+ def from_raw_profiles(
342
+ cls,
343
+ raw_profiles: Dict[str, Any],
344
+ profile_name: str,
345
+ renderer: Any, # ProfileRenderer
346
+ target_override: Optional[str] = None,
347
+ threads_override: Optional[int] = None,
348
+ ) -> "DVTProfile":
349
+ """
350
+ Create DVTProfile from raw profiles dict.
351
+
352
+ :param raw_profiles: The profile data, from disk as yaml
353
+ :param profile_name: The profile name to use
354
+ :param renderer: The config renderer
355
+ :param target_override: The target to use, if provided
356
+ :param threads_override: The thread count to use, if provided
357
+ :returns: The new DVTProfile object
358
+ """
359
+ from dbt.exceptions import DbtProjectError
360
+
361
+ if profile_name not in raw_profiles:
362
+ raise DbtProjectError(f"Could not find profile named '{profile_name}'")
363
+
364
+ raw_profile = raw_profiles[profile_name]
365
+ if not raw_profile:
366
+ raise DbtProfileError(f"Profile {profile_name} in profiles.yml is empty")
367
+
368
+ return cls.from_raw_profile_info(
369
+ raw_profile=raw_profile,
370
+ profile_name=profile_name,
371
+ renderer=renderer,
372
+ target_override=target_override,
373
+ threads_override=threads_override,
374
+ )
375
+
376
+ @classmethod
377
+ def render(
378
+ cls,
379
+ renderer: Any, # ProfileRenderer
380
+ project_profile_name: Optional[str],
381
+ profile_name_override: Optional[str] = None,
382
+ target_override: Optional[str] = None,
383
+ threads_override: Optional[int] = None,
384
+ ) -> "DVTProfile":
385
+ """
386
+ Render a DVTProfile from disk.
387
+
388
+ :param renderer: The config renderer
389
+ :param project_profile_name: The profile name from project
390
+ :param profile_name_override: Profile name override from CLI
391
+ :param target_override: Target override from CLI
392
+ :param threads_override: Threads override from CLI
393
+ :returns: The new DVTProfile object
394
+ """
395
+ from dbt.config.profile import read_profile
396
+ from dbt.flags import get_flags
397
+
398
+ flags = get_flags()
399
+ raw_profiles = read_profile(flags.PROFILES_DIR)
400
+ profile_name = cls.pick_profile_name(profile_name_override, project_profile_name)
401
+
402
+ return cls.from_raw_profiles(
403
+ raw_profiles=raw_profiles,
404
+ profile_name=profile_name,
405
+ renderer=renderer,
406
+ target_override=target_override,
407
+ threads_override=threads_override,
408
+ )