dvt-core 1.11.0b4__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.

Potentially problematic release.


This version of dvt-core might be problematic. Click here for more details.

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
dvt/cli/requires.py ADDED
@@ -0,0 +1,478 @@
1
+ import importlib.util
2
+ import os
3
+ import time
4
+ import traceback
5
+ from functools import update_wrapper
6
+ from typing import Dict, Optional
7
+
8
+ import dvt.tracking
9
+ from click import Context
10
+ from dvt.cli.exceptions import ExceptionExit, ResultExit
11
+ from dvt.cli.flags import Flags
12
+ from dvt.config import RuntimeConfig
13
+ from dvt.config.catalogs import get_active_write_integration, load_catalogs
14
+ from dvt.config.profiles_v2 import (
15
+ ProfileRegistry,
16
+ UnifiedProfileConfig,
17
+ load_unified_profiles,
18
+ )
19
+ from dvt.config.runtime import UnsetProfile, load_profile, load_project
20
+ from dvt.context.providers import generate_runtime_macro_context
21
+ from dvt.context.query_header import generate_query_header_context
22
+ from dvt.deprecations import show_deprecations_summary
23
+ from dvt.env_vars import KNOWN_ENGINE_ENV_VARS, validate_engine_env_vars
24
+ from dvt.events.logging import setup_event_logger
25
+ from dvt.events.types import (
26
+ ArtifactUploadError,
27
+ CommandCompleted,
28
+ MainEncounteredError,
29
+ MainReportArgs,
30
+ MainReportVersion,
31
+ MainStackTrace,
32
+ MainTrackingUserState,
33
+ ResourceReport,
34
+ )
35
+ from dvt.exceptions import DbtProjectError, FailFastError
36
+ from dvt.flags import get_flag_dict, get_flags, set_flags
37
+ from dvt.mp_context import get_mp_context
38
+ from dvt.parser.manifest import parse_manifest
39
+ from dvt.plugins import set_up_plugin_manager
40
+ from dvt.profiler import profiler
41
+ from dvt.tracking import active_user, initialize_from_flags, track_run
42
+ from dvt.utils import try_get_max_rss_kb
43
+ from dvt.utils.artifact_upload import upload_artifacts
44
+ from dvt.version import installed as installed_version
45
+
46
+ from dbt.adapters.factory import adapter_management, get_adapter, register_adapter
47
+ from dbt_common.clients.system import get_env
48
+ from dbt_common.context import get_invocation_context, set_invocation_context
49
+ from dbt_common.events.base_types import EventLevel
50
+ from dbt_common.events.event_manager_client import get_event_manager
51
+ from dbt_common.events.functions import LOG_VERSION, fire_event
52
+ from dbt_common.events.helpers import get_json_string_utcnow
53
+ from dbt_common.exceptions import DbtBaseException as DbtException
54
+ from dbt_common.invocation import reset_invocation_id
55
+ from dbt_common.record import (
56
+ Recorder,
57
+ RecorderMode,
58
+ get_record_mode_from_env,
59
+ get_record_types_from_dict,
60
+ get_record_types_from_env,
61
+ )
62
+ from dbt_common.utils import cast_dict_to_dict_of_strings
63
+
64
+
65
+ def _cross_propagate_engine_env_vars(env_dict: Dict[str, str]) -> None:
66
+ for env_var in KNOWN_ENGINE_ENV_VARS:
67
+ if env_var.old_name is not None:
68
+ # If the old name is in the env dict, and not the new name, set the new name based on the old name
69
+ if env_var.old_name in env_dict and env_var.name not in env_dict:
70
+ env_dict[env_var.name] = env_dict[env_var.old_name]
71
+ # If the new name is in the env dict, override the old name with it
72
+ elif env_var.name in env_dict:
73
+ env_dict[env_var.old_name] = env_dict[env_var.name]
74
+
75
+
76
+ def preflight(func):
77
+ def wrapper(*args, **kwargs):
78
+ ctx = args[0]
79
+ assert isinstance(ctx, Context)
80
+ ctx.obj = ctx.obj or {}
81
+
82
+ set_invocation_context({})
83
+
84
+ # Record/Replay
85
+ setup_record_replay()
86
+
87
+ # Must be set after record/replay is set up so that the env can be
88
+ # recorded or replayed if needed.
89
+ env_dict = get_env()
90
+ _cross_propagate_engine_env_vars(env_dict)
91
+ get_invocation_context()._env = env_dict
92
+
93
+ # Flags
94
+ flags = Flags(ctx)
95
+ ctx.obj["flags"] = flags
96
+ set_flags(flags)
97
+ get_event_manager().require_warn_or_error_handling = (
98
+ flags.require_all_warnings_handled_by_warn_error
99
+ )
100
+
101
+ # Reset invocation_id for each 'invocation' of a dbt command (can happen multiple times in a single process)
102
+ reset_invocation_id()
103
+
104
+ # Logging
105
+ callbacks = ctx.obj.get("callbacks", [])
106
+ setup_event_logger(flags=flags, callbacks=callbacks)
107
+
108
+ # Tracking
109
+ initialize_from_flags(flags.SEND_ANONYMOUS_USAGE_STATS, flags.PROFILES_DIR)
110
+ ctx.with_resource(track_run(run_command=flags.WHICH))
111
+
112
+ # Now that we have our logger, fire away!
113
+ fire_event(MainReportVersion(version=str(installed_version), log_version=LOG_VERSION))
114
+ flags_dict_str = cast_dict_to_dict_of_strings(get_flag_dict())
115
+ fire_event(MainReportArgs(args=flags_dict_str))
116
+
117
+ # Deprecation warnings
118
+ flags.fire_deprecations(ctx)
119
+
120
+ if active_user is not None: # mypy appeasement, always true
121
+ fire_event(MainTrackingUserState(user_state=active_user.state()))
122
+
123
+ # Profiling
124
+ if flags.RECORD_TIMING_INFO:
125
+ ctx.with_resource(profiler(enable=True, outfile=flags.RECORD_TIMING_INFO))
126
+
127
+ # Adapter management
128
+ ctx.with_resource(adapter_management())
129
+
130
+ # Validate engine env var restricted name space
131
+ validate_engine_env_vars()
132
+
133
+ return func(*args, **kwargs)
134
+
135
+ return update_wrapper(wrapper, func)
136
+
137
+
138
+ def setup_record_replay():
139
+ rec_mode = get_record_mode_from_env()
140
+ rec_types = get_record_types_from_env()
141
+
142
+ recorder: Optional[Recorder] = None
143
+ if rec_mode == RecorderMode.REPLAY:
144
+ previous_recording_path = os.environ.get(
145
+ "DBT_ENGINE_RECORDER_FILE_PATH"
146
+ ) or os.environ.get("DBT_RECORDER_FILE_PATH")
147
+ recorder = Recorder(
148
+ RecorderMode.REPLAY, types=rec_types, previous_recording_path=previous_recording_path
149
+ )
150
+ elif rec_mode == RecorderMode.DIFF:
151
+ previous_recording_path = os.environ.get(
152
+ "DBT_ENGINE_RECORDER_FILE_PATH"
153
+ ) or os.environ.get("DBT_RECORDER_FILE_PATH")
154
+ # ensure types match the previous recording
155
+ types = get_record_types_from_dict(previous_recording_path)
156
+ recorder = Recorder(
157
+ RecorderMode.DIFF, types=types, previous_recording_path=previous_recording_path
158
+ )
159
+ elif rec_mode == RecorderMode.RECORD:
160
+ recorder = Recorder(RecorderMode.RECORD, types=rec_types)
161
+
162
+ get_invocation_context().recorder = recorder
163
+
164
+
165
+ def tear_down_record_replay():
166
+ recorder = get_invocation_context().recorder
167
+ if recorder is not None:
168
+ if recorder.mode == RecorderMode.RECORD:
169
+ recorder.write()
170
+ if recorder.mode == RecorderMode.DIFF:
171
+ recorder.write()
172
+ recorder.write_diffs(diff_file_name="recording_diffs.json")
173
+ elif recorder.mode == RecorderMode.REPLAY:
174
+ recorder.write_diffs("replay_diffs.json")
175
+
176
+
177
+ def postflight(func):
178
+ """The decorator that handles all exception handling for the click commands.
179
+ This decorator must be used before any other decorators that may throw an exception."""
180
+
181
+ def wrapper(*args, **kwargs):
182
+ ctx = args[0]
183
+ start_func = time.perf_counter()
184
+ success = False
185
+
186
+ try:
187
+ result, success = func(*args, **kwargs)
188
+ except FailFastError as e:
189
+ fire_event(MainEncounteredError(exc=str(e)))
190
+ raise ResultExit(e.result)
191
+ except DbtException as e:
192
+ fire_event(MainEncounteredError(exc=str(e)))
193
+ raise ExceptionExit(e)
194
+ except BaseException as e:
195
+ fire_event(MainEncounteredError(exc=str(e)))
196
+ fire_event(MainStackTrace(stack_trace=traceback.format_exc()))
197
+ raise ExceptionExit(e)
198
+ finally:
199
+ # Fire ResourceReport, but only on systems which support the resource
200
+ # module. (Skip it on Windows).
201
+ try:
202
+ if get_flags().upload_to_artifacts_ingest_api:
203
+ upload_artifacts(
204
+ get_flags().project_dir, get_flags().target_path, ctx.command.name
205
+ )
206
+
207
+ except Exception as e:
208
+ fire_event(ArtifactUploadError(msg=str(e)))
209
+
210
+ show_deprecations_summary()
211
+
212
+ if importlib.util.find_spec("resource") is not None:
213
+ import resource
214
+
215
+ rusage = resource.getrusage(resource.RUSAGE_SELF)
216
+ fire_event(
217
+ ResourceReport(
218
+ command_name=ctx.command.name,
219
+ command_success=success,
220
+ command_wall_clock_time=time.perf_counter() - start_func,
221
+ process_user_time=rusage.ru_utime,
222
+ process_kernel_time=rusage.ru_stime,
223
+ process_mem_max_rss=try_get_max_rss_kb() or rusage.ru_maxrss,
224
+ process_in_blocks=rusage.ru_inblock,
225
+ process_out_blocks=rusage.ru_oublock,
226
+ ),
227
+ (
228
+ EventLevel.INFO
229
+ if "flags" in ctx.obj and ctx.obj["flags"].SHOW_RESOURCE_REPORT
230
+ else None
231
+ ),
232
+ )
233
+
234
+ fire_event(
235
+ CommandCompleted(
236
+ command=ctx.command_path,
237
+ success=success,
238
+ completed_at=get_json_string_utcnow(),
239
+ elapsed=time.perf_counter() - start_func,
240
+ )
241
+ )
242
+
243
+ tear_down_record_replay()
244
+
245
+ if not success:
246
+ raise ResultExit(result)
247
+
248
+ return (result, success)
249
+
250
+ return update_wrapper(wrapper, func)
251
+
252
+
253
+ # TODO: UnsetProfile is necessary for deps and clean to load a project.
254
+ # This decorator and its usage can be removed once https://github.com/dbt-labs/dbt-core/issues/6257 is closed.
255
+ def unset_profile(func):
256
+ def wrapper(*args, **kwargs):
257
+ ctx = args[0]
258
+ assert isinstance(ctx, Context)
259
+
260
+ profile = UnsetProfile()
261
+ ctx.obj["profile"] = profile
262
+
263
+ return func(*args, **kwargs)
264
+
265
+ return update_wrapper(wrapper, func)
266
+
267
+
268
+ def profile(func):
269
+ def wrapper(*args, **kwargs):
270
+ ctx = args[0]
271
+ assert isinstance(ctx, Context)
272
+
273
+ flags = ctx.obj["flags"]
274
+ # TODO: Generalize safe access to flags.THREADS:
275
+ # https://github.com/dbt-labs/dbt-core/issues/6259
276
+ threads = getattr(flags, "THREADS", None)
277
+ profile = load_profile(flags.PROJECT_DIR, flags.VARS, flags.PROFILE, flags.TARGET, threads)
278
+ ctx.obj["profile"] = profile
279
+ get_invocation_context().uses_adapter(profile.credentials.type)
280
+
281
+ return func(*args, **kwargs)
282
+
283
+ return update_wrapper(wrapper, func)
284
+
285
+
286
+ def project(func):
287
+ def wrapper(*args, **kwargs):
288
+ ctx = args[0]
289
+ assert isinstance(ctx, Context)
290
+
291
+ # TODO: Decouple target from profile, and remove the need for profile here:
292
+ # https://github.com/dbt-labs/dbt-core/issues/6257
293
+ if not ctx.obj.get("profile"):
294
+ raise DbtProjectError("profile required for project")
295
+
296
+ flags = ctx.obj["flags"]
297
+ # TODO deprecations warnings fired from loading the project will lack
298
+ # the project_id in the snowplow event.
299
+ project = load_project(
300
+ flags.PROJECT_DIR, flags.VERSION_CHECK, ctx.obj["profile"], flags.VARS, validate=True
301
+ )
302
+ ctx.obj["project"] = project
303
+
304
+ # Plugins
305
+ set_up_plugin_manager(project_name=project.project_name)
306
+
307
+ if dbt.tracking.active_user is not None:
308
+ project_id = None if project is None else project.hashed_name()
309
+
310
+ dbt.tracking.track_project_id({"project_id": project_id})
311
+
312
+ return func(*args, **kwargs)
313
+
314
+ return update_wrapper(wrapper, func)
315
+
316
+
317
+ def runtime_config(func):
318
+ """A decorator used by click command functions for generating a runtime
319
+ config given a profile and project.
320
+ """
321
+
322
+ def wrapper(*args, **kwargs):
323
+ ctx = args[0]
324
+ assert isinstance(ctx, Context)
325
+
326
+ req_strs = ["profile", "project"]
327
+ reqs = [ctx.obj.get(req_str) for req_str in req_strs]
328
+
329
+ if None in reqs:
330
+ raise DbtProjectError("profile and project required for runtime_config")
331
+
332
+ config = RuntimeConfig.from_parts(
333
+ ctx.obj["project"],
334
+ ctx.obj["profile"],
335
+ ctx.obj["flags"],
336
+ )
337
+
338
+ ctx.obj["runtime_config"] = config
339
+
340
+ if dbt.tracking.active_user is not None:
341
+ adapter_type = (
342
+ getattr(config.credentials, "type", None)
343
+ if hasattr(config, "credentials")
344
+ else None
345
+ )
346
+ adapter_unique_id = (
347
+ config.credentials.hashed_unique_field()
348
+ if hasattr(config, "credentials")
349
+ else None
350
+ )
351
+
352
+ dbt.tracking.track_adapter_info(
353
+ {
354
+ "adapter_type": adapter_type,
355
+ "adapter_unique_id": adapter_unique_id,
356
+ }
357
+ )
358
+
359
+ return func(*args, **kwargs)
360
+
361
+ return update_wrapper(wrapper, func)
362
+
363
+
364
+ def profiles_registry(func):
365
+ """
366
+ A decorator for commands that need to work with multiple profiles.
367
+
368
+ This decorator loads all profiles from profiles.yml and creates a ProfileRegistry
369
+ that can be used to access multiple database connections simultaneously.
370
+
371
+ Use this for DVT-specific commands like:
372
+ - dvt debug --all-profiles
373
+ - dvt profiles list/test
374
+ - dvt compute test
375
+
376
+ The decorator adds 'profiles_registry' to ctx.obj.
377
+ """
378
+
379
+ def wrapper(*args, **kwargs):
380
+ ctx = args[0]
381
+ assert isinstance(ctx, Context)
382
+
383
+ flags = ctx.obj.get("flags")
384
+ if not flags:
385
+ raise DbtProjectError("flags required for profiles_registry")
386
+
387
+ # Load unified profiles from standard locations
388
+ from pathlib import Path
389
+
390
+ project_dir = Path(flags.PROJECT_DIR) if flags.PROJECT_DIR else None
391
+ unified_profiles = load_unified_profiles(project_dir)
392
+
393
+ # Create ProfileRegistry
394
+ registry = ProfileRegistry(unified_profiles)
395
+ ctx.obj["profiles_registry"] = registry
396
+ ctx.obj["unified_profiles"] = unified_profiles
397
+
398
+ return func(*args, **kwargs)
399
+
400
+ return update_wrapper(wrapper, func)
401
+
402
+
403
+ def catalogs(func):
404
+ """A decorator used by click command functions for loading catalogs"""
405
+
406
+ def wrapper(*args, **kwargs):
407
+ ctx = args[0]
408
+ assert isinstance(ctx, Context)
409
+
410
+ req_strs = ["flags", "profile", "project"]
411
+ reqs = [ctx.obj.get(req_str) for req_str in req_strs]
412
+ if None in reqs:
413
+ raise DbtProjectError("profile and flags required to load catalogs")
414
+
415
+ flags = ctx.obj["flags"]
416
+ ctx_project = ctx.obj["project"]
417
+
418
+ _catalogs = load_catalogs(flags.PROJECT_DIR, ctx_project.project_name, flags.VARS)
419
+ ctx.obj["catalogs"] = _catalogs
420
+
421
+ return func(*args, **kwargs)
422
+
423
+ return update_wrapper(wrapper, func)
424
+
425
+
426
+ def manifest(*args0, write=True, write_perf_info=False):
427
+ """A decorator used by click command functions for generating a manifest
428
+ given a profile, project, and runtime config. This also registers the adapter
429
+ from the runtime config and conditionally writes the manifest to disk.
430
+ """
431
+
432
+ def outer_wrapper(func):
433
+ def wrapper(*args, **kwargs):
434
+ ctx = args[0]
435
+ assert isinstance(ctx, Context)
436
+
437
+ setup_manifest(ctx, write=write, write_perf_info=write_perf_info)
438
+ return func(*args, **kwargs)
439
+
440
+ return update_wrapper(wrapper, func)
441
+
442
+ # if there are no args, the decorator was used without params @decorator
443
+ # otherwise, the decorator was called with params @decorator(arg)
444
+ if len(args0) == 0:
445
+ return outer_wrapper
446
+ return outer_wrapper(args0[0])
447
+
448
+
449
+ def setup_manifest(ctx: Context, write: bool = True, write_perf_info: bool = False):
450
+ """Load the manifest and add it to the context."""
451
+ req_strs = ["profile", "project", "runtime_config"]
452
+ reqs = [ctx.obj.get(dep) for dep in req_strs]
453
+
454
+ if None in reqs:
455
+ raise DbtProjectError("profile, project, and runtime_config required for manifest")
456
+
457
+ runtime_config = ctx.obj["runtime_config"]
458
+
459
+ catalogs = ctx.obj["catalogs"] if "catalogs" in ctx.obj else []
460
+ active_integrations = [get_active_write_integration(catalog) for catalog in catalogs]
461
+
462
+ # if a manifest has already been set on the context, don't overwrite it
463
+ if ctx.obj.get("manifest") is None:
464
+ ctx.obj["manifest"] = parse_manifest(
465
+ runtime_config,
466
+ write_perf_info,
467
+ write,
468
+ ctx.obj["flags"].write_json,
469
+ active_integrations,
470
+ )
471
+ adapter = get_adapter(runtime_config)
472
+ else:
473
+ register_adapter(runtime_config, get_mp_context())
474
+ adapter = get_adapter(runtime_config)
475
+ adapter.set_macro_context_generator(generate_runtime_macro_context) # type: ignore[arg-type]
476
+ adapter.set_macro_resolver(ctx.obj["manifest"])
477
+ query_header_context = generate_query_header_context(adapter.config, ctx.obj["manifest"]) # type: ignore[attr-defined]
478
+ adapter.connections.set_query_header(query_header_context)
dvt/cli/resolvers.py ADDED
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+
3
+ from dvt.config.project import PartialProject
4
+ from dvt.exceptions import DbtProjectError
5
+
6
+
7
+ def default_project_dir() -> Path:
8
+ paths = list(Path.cwd().parents)
9
+ paths.insert(0, Path.cwd())
10
+ return next((x for x in paths if (x / "dbt_project.yml").exists()), Path.cwd())
11
+
12
+
13
+ def default_profiles_dir() -> Path:
14
+ return Path.cwd() if (Path.cwd() / "profiles.yml").exists() else Path.home() / ".dbt"
15
+
16
+
17
+ def default_log_path(project_dir: Path, verify_version: bool = False) -> Path:
18
+ """If available, derive a default log path from dbt_project.yml. Otherwise, default to "logs".
19
+ Known limitations:
20
+ 1. Using PartialProject here, so no jinja rendering of log-path.
21
+ 2. Programmatic invocations of the cli via dbtRunner may pass a Project object directly,
22
+ which is not being taken into consideration here to extract a log-path.
23
+ """
24
+ default_log_path = Path("logs")
25
+ try:
26
+ partial = PartialProject.from_project_root(str(project_dir), verify_version=verify_version)
27
+ partial_log_path = partial.project_dict.get("log-path") or default_log_path
28
+ default_log_path = Path(project_dir) / partial_log_path
29
+ except DbtProjectError:
30
+ pass
31
+
32
+ return default_log_path
dvt/cli/types.py ADDED
@@ -0,0 +1,40 @@
1
+ from enum import Enum
2
+ from typing import List
3
+
4
+ from dbt_common.exceptions import DbtInternalError
5
+
6
+
7
+ class Command(Enum):
8
+ BUILD = "build"
9
+ CLEAN = "clean"
10
+ COMPILE = "compile"
11
+ CLONE = "clone"
12
+ DOCS_GENERATE = "generate"
13
+ DOCS_SERVE = "serve"
14
+ DEBUG = "debug"
15
+ DEPS = "deps"
16
+ INIT = "init"
17
+ LIST = "list"
18
+ PARSE = "parse"
19
+ RUN = "run"
20
+ RUN_OPERATION = "run-operation"
21
+ SEED = "seed"
22
+ SHOW = "show"
23
+ SNAPSHOT = "snapshot"
24
+ SOURCE_FRESHNESS = "freshness"
25
+ TEST = "test"
26
+ RETRY = "retry"
27
+
28
+ @classmethod
29
+ def from_str(cls, s: str) -> "Command":
30
+ try:
31
+ return cls(s)
32
+ except ValueError:
33
+ raise DbtInternalError(f"No value '{s}' exists in Command enum")
34
+
35
+ def to_list(self) -> List[str]:
36
+ return {
37
+ Command.DOCS_GENERATE: ["docs", "generate"],
38
+ Command.DOCS_SERVE: ["docs", "serve"],
39
+ Command.SOURCE_FRESHNESS: ["source", "freshness"],
40
+ }.get(self, [self.value])
File without changes
@@ -0,0 +1,82 @@
1
+ import collections
2
+ import dataclasses
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+
5
+ import yaml
6
+ from dvt import deprecations
7
+ from dvt.clients.yaml_helper import load_yaml_text
8
+
9
+ # the C version is faster, but it doesn't always exist
10
+ try:
11
+ from yaml import CSafeLoader as SafeLoader
12
+ except ImportError:
13
+ from yaml import SafeLoader # type: ignore # noqa: F401
14
+
15
+
16
+ @dataclasses.dataclass
17
+ class YamlCheckFailure:
18
+ failure_type: str
19
+ message: str
20
+
21
+
22
+ def checked_load(contents) -> Tuple[Optional[Dict[str, Any]], List[YamlCheckFailure]]:
23
+ # A hacky (but sadly justified) method for modifying a bit of PyYAML. We create
24
+ # a new local subclass of SafeLoader, since we need to associate state with
25
+ # the static class, but static classes do not have non-static state. This allows
26
+ # us to be sure we have exclusive access to the class.
27
+ class CheckedLoader(SafeLoader):
28
+ check_failures: List[YamlCheckFailure] = []
29
+
30
+ def construct_mapping(self, node, deep=False):
31
+ if not isinstance(node, yaml.MappingNode):
32
+ raise yaml.constructor.ConstructorError(
33
+ None, None, "expected a mapping node, but found %s" % node.id, node.start_mark
34
+ )
35
+ is_override = (
36
+ len(node.value) > 0
37
+ and len(node.value[0]) > 0
38
+ and getattr(node.value[0][0], "value") == "<<"
39
+ )
40
+ self.flatten_mapping(node)
41
+ mapping = {}
42
+ for key_node, value_node in node.value:
43
+ key = self.construct_object(key_node, deep=deep)
44
+ if not isinstance(key, collections.abc.Hashable):
45
+ raise yaml.constructor.ConstructorError(
46
+ "while constructing a mapping",
47
+ node.start_mark,
48
+ "found unhashable key",
49
+ key_node.start_mark,
50
+ )
51
+ value = self.construct_object(value_node, deep=deep)
52
+
53
+ if not is_override and key in mapping:
54
+ start_mark = str(key_node.start_mark)
55
+ if start_mark.startswith(" in"): # this means it was at the top level
56
+ message = f"Duplicate key '{key}' {start_mark.lstrip()}"
57
+ else:
58
+ message = f"Duplicate key '{key}' at {key_node.start_mark}"
59
+
60
+ self.check_failures.append(YamlCheckFailure("duplicate_key", message))
61
+
62
+ mapping[key] = value
63
+ return mapping
64
+
65
+ CheckedLoader.add_constructor(
66
+ yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, CheckedLoader.construct_mapping
67
+ )
68
+
69
+ dct = load_yaml_text(contents, loader=CheckedLoader)
70
+ check_failures = CheckedLoader.check_failures
71
+
72
+ return (dct, check_failures)
73
+
74
+
75
+ def issue_deprecation_warnings_for_failures(failures: List[YamlCheckFailure], file: str):
76
+ for failure in failures:
77
+ if failure.failure_type == "duplicate_key":
78
+ deprecations.warn(
79
+ "duplicate-yaml-keys-deprecation",
80
+ duplicate_description=failure.message,
81
+ file=file,
82
+ )