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.
- dvt/__init__.py +7 -0
- dvt/_pydantic_shim.py +26 -0
- dvt/adapters/__init__.py +16 -0
- dvt/adapters/multi_adapter_manager.py +268 -0
- dvt/artifacts/__init__.py +0 -0
- dvt/artifacts/exceptions/__init__.py +1 -0
- dvt/artifacts/exceptions/schemas.py +31 -0
- dvt/artifacts/resources/__init__.py +116 -0
- dvt/artifacts/resources/base.py +68 -0
- dvt/artifacts/resources/types.py +93 -0
- dvt/artifacts/resources/v1/analysis.py +10 -0
- dvt/artifacts/resources/v1/catalog.py +23 -0
- dvt/artifacts/resources/v1/components.py +275 -0
- dvt/artifacts/resources/v1/config.py +282 -0
- dvt/artifacts/resources/v1/documentation.py +11 -0
- dvt/artifacts/resources/v1/exposure.py +52 -0
- dvt/artifacts/resources/v1/function.py +53 -0
- dvt/artifacts/resources/v1/generic_test.py +32 -0
- dvt/artifacts/resources/v1/group.py +22 -0
- dvt/artifacts/resources/v1/hook.py +11 -0
- dvt/artifacts/resources/v1/macro.py +30 -0
- dvt/artifacts/resources/v1/metric.py +173 -0
- dvt/artifacts/resources/v1/model.py +146 -0
- dvt/artifacts/resources/v1/owner.py +10 -0
- dvt/artifacts/resources/v1/saved_query.py +112 -0
- dvt/artifacts/resources/v1/seed.py +42 -0
- dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
- dvt/artifacts/resources/v1/semantic_model.py +315 -0
- dvt/artifacts/resources/v1/singular_test.py +14 -0
- dvt/artifacts/resources/v1/snapshot.py +92 -0
- dvt/artifacts/resources/v1/source_definition.py +85 -0
- dvt/artifacts/resources/v1/sql_operation.py +10 -0
- dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
- dvt/artifacts/schemas/__init__.py +0 -0
- dvt/artifacts/schemas/base.py +191 -0
- dvt/artifacts/schemas/batch_results.py +24 -0
- dvt/artifacts/schemas/catalog/__init__.py +12 -0
- dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
- dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
- dvt/artifacts/schemas/freshness/__init__.py +1 -0
- dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
- dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
- dvt/artifacts/schemas/manifest/__init__.py +2 -0
- dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
- dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
- dvt/artifacts/schemas/results.py +148 -0
- dvt/artifacts/schemas/run/__init__.py +2 -0
- dvt/artifacts/schemas/run/v5/__init__.py +0 -0
- dvt/artifacts/schemas/run/v5/run.py +184 -0
- dvt/artifacts/schemas/upgrades/__init__.py +4 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
- dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
- dvt/artifacts/utils/validation.py +153 -0
- dvt/cli/__init__.py +1 -0
- dvt/cli/context.py +16 -0
- dvt/cli/exceptions.py +56 -0
- dvt/cli/flags.py +558 -0
- dvt/cli/main.py +971 -0
- dvt/cli/option_types.py +121 -0
- dvt/cli/options.py +79 -0
- dvt/cli/params.py +803 -0
- dvt/cli/requires.py +478 -0
- dvt/cli/resolvers.py +32 -0
- dvt/cli/types.py +40 -0
- dvt/clients/__init__.py +0 -0
- dvt/clients/checked_load.py +82 -0
- dvt/clients/git.py +164 -0
- dvt/clients/jinja.py +206 -0
- dvt/clients/jinja_static.py +245 -0
- dvt/clients/registry.py +192 -0
- dvt/clients/yaml_helper.py +68 -0
- dvt/compilation.py +833 -0
- dvt/compute/__init__.py +26 -0
- dvt/compute/base.py +288 -0
- dvt/compute/engines/__init__.py +13 -0
- dvt/compute/engines/duckdb_engine.py +368 -0
- dvt/compute/engines/spark_engine.py +273 -0
- dvt/compute/query_analyzer.py +212 -0
- dvt/compute/router.py +483 -0
- dvt/config/__init__.py +4 -0
- dvt/config/catalogs.py +95 -0
- dvt/config/compute_config.py +406 -0
- dvt/config/profile.py +411 -0
- dvt/config/profiles_v2.py +464 -0
- dvt/config/project.py +893 -0
- dvt/config/renderer.py +232 -0
- dvt/config/runtime.py +491 -0
- dvt/config/selectors.py +209 -0
- dvt/config/utils.py +78 -0
- dvt/connectors/.gitignore +6 -0
- dvt/connectors/README.md +306 -0
- dvt/connectors/catalog.yml +217 -0
- dvt/connectors/download_connectors.py +300 -0
- dvt/constants.py +29 -0
- dvt/context/__init__.py +0 -0
- dvt/context/base.py +746 -0
- dvt/context/configured.py +136 -0
- dvt/context/context_config.py +350 -0
- dvt/context/docs.py +82 -0
- dvt/context/exceptions_jinja.py +179 -0
- dvt/context/macro_resolver.py +195 -0
- dvt/context/macros.py +171 -0
- dvt/context/manifest.py +73 -0
- dvt/context/providers.py +2198 -0
- dvt/context/query_header.py +14 -0
- dvt/context/secret.py +59 -0
- dvt/context/target.py +74 -0
- dvt/contracts/__init__.py +0 -0
- dvt/contracts/files.py +413 -0
- dvt/contracts/graph/__init__.py +0 -0
- dvt/contracts/graph/manifest.py +1904 -0
- dvt/contracts/graph/metrics.py +98 -0
- dvt/contracts/graph/model_config.py +71 -0
- dvt/contracts/graph/node_args.py +42 -0
- dvt/contracts/graph/nodes.py +1806 -0
- dvt/contracts/graph/semantic_manifest.py +233 -0
- dvt/contracts/graph/unparsed.py +812 -0
- dvt/contracts/project.py +417 -0
- dvt/contracts/results.py +53 -0
- dvt/contracts/selection.py +23 -0
- dvt/contracts/sql.py +86 -0
- dvt/contracts/state.py +69 -0
- dvt/contracts/util.py +46 -0
- dvt/deprecations.py +347 -0
- dvt/deps/__init__.py +0 -0
- dvt/deps/base.py +153 -0
- dvt/deps/git.py +196 -0
- dvt/deps/local.py +80 -0
- dvt/deps/registry.py +131 -0
- dvt/deps/resolver.py +149 -0
- dvt/deps/tarball.py +121 -0
- dvt/docs/source/_ext/dbt_click.py +118 -0
- dvt/docs/source/conf.py +32 -0
- dvt/env_vars.py +64 -0
- dvt/event_time/event_time.py +40 -0
- dvt/event_time/sample_window.py +60 -0
- dvt/events/__init__.py +16 -0
- dvt/events/base_types.py +37 -0
- dvt/events/core_types_pb2.py +2 -0
- dvt/events/logging.py +109 -0
- dvt/events/types.py +2534 -0
- dvt/exceptions.py +1487 -0
- dvt/flags.py +89 -0
- dvt/graph/__init__.py +11 -0
- dvt/graph/cli.py +248 -0
- dvt/graph/graph.py +172 -0
- dvt/graph/queue.py +213 -0
- dvt/graph/selector.py +375 -0
- dvt/graph/selector_methods.py +976 -0
- dvt/graph/selector_spec.py +223 -0
- dvt/graph/thread_pool.py +18 -0
- dvt/hooks.py +21 -0
- dvt/include/README.md +49 -0
- dvt/include/__init__.py +3 -0
- dvt/include/global_project.py +4 -0
- dvt/include/starter_project/.gitignore +4 -0
- dvt/include/starter_project/README.md +15 -0
- dvt/include/starter_project/__init__.py +3 -0
- dvt/include/starter_project/analyses/.gitkeep +0 -0
- dvt/include/starter_project/dvt_project.yml +36 -0
- dvt/include/starter_project/macros/.gitkeep +0 -0
- dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
- dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
- dvt/include/starter_project/models/example/schema.yml +21 -0
- dvt/include/starter_project/seeds/.gitkeep +0 -0
- dvt/include/starter_project/snapshots/.gitkeep +0 -0
- dvt/include/starter_project/tests/.gitkeep +0 -0
- dvt/internal_deprecations.py +27 -0
- dvt/jsonschemas/__init__.py +3 -0
- dvt/jsonschemas/jsonschemas.py +309 -0
- dvt/jsonschemas/project/0.0.110.json +4717 -0
- dvt/jsonschemas/project/0.0.85.json +2015 -0
- dvt/jsonschemas/resources/0.0.110.json +2636 -0
- dvt/jsonschemas/resources/0.0.85.json +2536 -0
- dvt/jsonschemas/resources/latest.json +6773 -0
- dvt/links.py +4 -0
- dvt/materializations/__init__.py +0 -0
- dvt/materializations/incremental/__init__.py +0 -0
- dvt/materializations/incremental/microbatch.py +235 -0
- dvt/mp_context.py +8 -0
- dvt/node_types.py +37 -0
- dvt/parser/__init__.py +23 -0
- dvt/parser/analysis.py +21 -0
- dvt/parser/base.py +549 -0
- dvt/parser/common.py +267 -0
- dvt/parser/docs.py +52 -0
- dvt/parser/fixtures.py +51 -0
- dvt/parser/functions.py +30 -0
- dvt/parser/generic_test.py +100 -0
- dvt/parser/generic_test_builders.py +334 -0
- dvt/parser/hooks.py +119 -0
- dvt/parser/macros.py +137 -0
- dvt/parser/manifest.py +2204 -0
- dvt/parser/models.py +574 -0
- dvt/parser/partial.py +1179 -0
- dvt/parser/read_files.py +445 -0
- dvt/parser/schema_generic_tests.py +423 -0
- dvt/parser/schema_renderer.py +111 -0
- dvt/parser/schema_yaml_readers.py +936 -0
- dvt/parser/schemas.py +1467 -0
- dvt/parser/search.py +149 -0
- dvt/parser/seeds.py +28 -0
- dvt/parser/singular_test.py +20 -0
- dvt/parser/snapshots.py +44 -0
- dvt/parser/sources.py +557 -0
- dvt/parser/sql.py +63 -0
- dvt/parser/unit_tests.py +622 -0
- dvt/plugins/__init__.py +20 -0
- dvt/plugins/contracts.py +10 -0
- dvt/plugins/exceptions.py +2 -0
- dvt/plugins/manager.py +164 -0
- dvt/plugins/manifest.py +21 -0
- dvt/profiler.py +20 -0
- dvt/py.typed +1 -0
- dvt/runners/__init__.py +2 -0
- dvt/runners/exposure_runner.py +7 -0
- dvt/runners/no_op_runner.py +46 -0
- dvt/runners/saved_query_runner.py +7 -0
- dvt/selected_resources.py +8 -0
- dvt/task/__init__.py +0 -0
- dvt/task/base.py +504 -0
- dvt/task/build.py +197 -0
- dvt/task/clean.py +57 -0
- dvt/task/clone.py +162 -0
- dvt/task/compile.py +151 -0
- dvt/task/compute.py +366 -0
- dvt/task/debug.py +650 -0
- dvt/task/deps.py +280 -0
- dvt/task/docs/__init__.py +3 -0
- dvt/task/docs/generate.py +408 -0
- dvt/task/docs/index.html +250 -0
- dvt/task/docs/serve.py +28 -0
- dvt/task/freshness.py +323 -0
- dvt/task/function.py +122 -0
- dvt/task/group_lookup.py +46 -0
- dvt/task/init.py +374 -0
- dvt/task/list.py +237 -0
- dvt/task/printer.py +176 -0
- dvt/task/profiles.py +256 -0
- dvt/task/retry.py +175 -0
- dvt/task/run.py +1146 -0
- dvt/task/run_operation.py +142 -0
- dvt/task/runnable.py +802 -0
- dvt/task/seed.py +104 -0
- dvt/task/show.py +150 -0
- dvt/task/snapshot.py +57 -0
- dvt/task/sql.py +111 -0
- dvt/task/test.py +464 -0
- dvt/tests/fixtures/__init__.py +1 -0
- dvt/tests/fixtures/project.py +620 -0
- dvt/tests/util.py +651 -0
- dvt/tracking.py +529 -0
- dvt/utils/__init__.py +3 -0
- dvt/utils/artifact_upload.py +151 -0
- dvt/utils/utils.py +408 -0
- dvt/version.py +249 -0
- dvt_core-1.11.0b4.dist-info/METADATA +252 -0
- dvt_core-1.11.0b4.dist-info/RECORD +261 -0
- dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
- dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
- 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])
|
dvt/clients/__init__.py
ADDED
|
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
|
+
)
|