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/tracking.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import traceback
|
|
4
|
+
import uuid
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import pytz
|
|
10
|
+
import requests
|
|
11
|
+
from dvt import version as dbt_version
|
|
12
|
+
from dvt.clients.yaml_helper import safe_load, yaml # noqa:F401
|
|
13
|
+
from dvt.events.types import (
|
|
14
|
+
DisableTracking,
|
|
15
|
+
FlushEvents,
|
|
16
|
+
FlushEventsFailure,
|
|
17
|
+
MainEncounteredError,
|
|
18
|
+
SendEventFailure,
|
|
19
|
+
SendingEvent,
|
|
20
|
+
TrackingInitializeFailure,
|
|
21
|
+
)
|
|
22
|
+
from packaging.version import Version
|
|
23
|
+
from snowplow_tracker import Emitter, SelfDescribingJson, Subject, Tracker
|
|
24
|
+
from snowplow_tracker import __version__ as snowplow_version # type: ignore
|
|
25
|
+
from snowplow_tracker import logger as sp_logger
|
|
26
|
+
from snowplow_tracker.events import StructuredEvent
|
|
27
|
+
|
|
28
|
+
from dbt.adapters.exceptions import FailedToConnectError
|
|
29
|
+
from dbt_common.events.base_types import EventMsg
|
|
30
|
+
from dbt_common.events.functions import fire_event, get_invocation_id, msg_to_dict
|
|
31
|
+
from dbt_common.exceptions import NotImplementedError
|
|
32
|
+
|
|
33
|
+
sp_logger.setLevel(100)
|
|
34
|
+
|
|
35
|
+
COLLECTOR_URL = "fishtownanalytics.sinter-collect.com"
|
|
36
|
+
COLLECTOR_PROTOCOL = "https"
|
|
37
|
+
DBT_INVOCATION_ENV = "DBT_INVOCATION_ENV"
|
|
38
|
+
|
|
39
|
+
ADAPTER_INFO_SPEC = "iglu:com.dbt/adapter_info/jsonschema/1-0-1"
|
|
40
|
+
DEPRECATION_WARN_SPEC = "iglu:com.dbt/deprecation_warn/jsonschema/1-0-0"
|
|
41
|
+
BEHAVIOR_CHANGE_WARN_SPEC = "iglu:com.dbt/behavior_change_warn/jsonschema/1-0-0"
|
|
42
|
+
EXPERIMENTAL_PARSER = "iglu:com.dbt/experimental_parser/jsonschema/1-0-0"
|
|
43
|
+
INVOCATION_ENV_SPEC = "iglu:com.dbt/invocation_env/jsonschema/1-0-0"
|
|
44
|
+
INVOCATION_SPEC = "iglu:com.dbt/invocation/jsonschema/1-0-2"
|
|
45
|
+
LOAD_ALL_TIMING_SPEC = "iglu:com.dbt/load_all_timing/jsonschema/1-0-3"
|
|
46
|
+
PACKAGE_INSTALL_SPEC = "iglu:com.dbt/package_install/jsonschema/1-0-0"
|
|
47
|
+
PARTIAL_PARSER = "iglu:com.dbt/partial_parser/jsonschema/1-0-1"
|
|
48
|
+
PLATFORM_SPEC = "iglu:com.dbt/platform/jsonschema/1-0-0"
|
|
49
|
+
PROJECT_ID_SPEC = "iglu:com.dbt/project_id/jsonschema/1-0-1"
|
|
50
|
+
RESOURCE_COUNTS = "iglu:com.dbt/resource_counts/jsonschema/1-0-1"
|
|
51
|
+
RPC_REQUEST_SPEC = "iglu:com.dbt/rpc_request/jsonschema/1-0-1"
|
|
52
|
+
RUNNABLE_TIMING = "iglu:com.dbt/runnable/jsonschema/1-0-0"
|
|
53
|
+
RUN_MODEL_SPEC = "iglu:com.dbt/run_model/jsonschema/1-1-0"
|
|
54
|
+
PLUGIN_GET_NODES = "iglu:com.dbt/plugin_get_nodes/jsonschema/1-0-0"
|
|
55
|
+
ARTIFACT_UPLOAD = "iglu:com.dbt/artifact_upload/jsonschema/1-0-0"
|
|
56
|
+
|
|
57
|
+
SNOWPLOW_TRACKER_VERSION = Version(snowplow_version)
|
|
58
|
+
|
|
59
|
+
# workaround in case real snowplow tracker is in the env
|
|
60
|
+
# the argument was renamed in https://github.com/snowplow/snowplow-python-tracker/commit/39fd50a3aff98a5efdd5c5c7fb5518fe4761305b
|
|
61
|
+
INIT_KW_ARGS = (
|
|
62
|
+
{"buffer_size": 30} if SNOWPLOW_TRACKER_VERSION < Version("0.13.0") else {"batch_size": 30}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TimeoutEmitter(Emitter):
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
super().__init__(
|
|
69
|
+
COLLECTOR_URL,
|
|
70
|
+
protocol=COLLECTOR_PROTOCOL,
|
|
71
|
+
on_failure=self.handle_failure,
|
|
72
|
+
method="post",
|
|
73
|
+
# don't set this.
|
|
74
|
+
byte_limit=None,
|
|
75
|
+
**INIT_KW_ARGS,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def handle_failure(num_ok, unsent):
|
|
80
|
+
# num_ok will always be 0, unsent will always be 1 entry long, because
|
|
81
|
+
# the buffer is length 1, so not much to talk about
|
|
82
|
+
fire_event(DisableTracking())
|
|
83
|
+
disable_tracking()
|
|
84
|
+
|
|
85
|
+
def _log_request(self, request, payload):
|
|
86
|
+
sp_logger.info(f"Sending {request} request to {self.endpoint}...")
|
|
87
|
+
sp_logger.debug(f"Payload: {payload}")
|
|
88
|
+
|
|
89
|
+
def _log_result(self, request, status_code):
|
|
90
|
+
msg = f"{request} request finished with status code: {status_code}"
|
|
91
|
+
if self.is_good_status_code(status_code):
|
|
92
|
+
sp_logger.info(msg)
|
|
93
|
+
else:
|
|
94
|
+
sp_logger.warning(msg)
|
|
95
|
+
|
|
96
|
+
def http_post(self, payload):
|
|
97
|
+
self._log_request("POST", payload)
|
|
98
|
+
|
|
99
|
+
r = requests.post(
|
|
100
|
+
self.endpoint,
|
|
101
|
+
data=payload,
|
|
102
|
+
headers={"content-type": "application/json; charset=utf-8"},
|
|
103
|
+
timeout=5.0,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self._log_result("GET", r.status_code)
|
|
107
|
+
return r
|
|
108
|
+
|
|
109
|
+
def http_get(self, payload):
|
|
110
|
+
self._log_request("GET", payload)
|
|
111
|
+
|
|
112
|
+
r = requests.get(self.endpoint, params=payload, timeout=5.0)
|
|
113
|
+
|
|
114
|
+
self._log_result("GET", r.status_code)
|
|
115
|
+
return r
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
emitter = TimeoutEmitter()
|
|
119
|
+
tracker = Tracker(
|
|
120
|
+
emitters=emitter,
|
|
121
|
+
namespace="cf",
|
|
122
|
+
app_id="dbt",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class User:
|
|
127
|
+
def __init__(self, cookie_dir) -> None:
|
|
128
|
+
self.do_not_track = True
|
|
129
|
+
self.cookie_dir = cookie_dir
|
|
130
|
+
|
|
131
|
+
self.id = None
|
|
132
|
+
self.invocation_id = get_invocation_id()
|
|
133
|
+
self.run_started_at = datetime.now(tz=pytz.utc)
|
|
134
|
+
|
|
135
|
+
def state(self):
|
|
136
|
+
return "do not track" if self.do_not_track else "tracking"
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def cookie_path(self):
|
|
140
|
+
return os.path.join(self.cookie_dir, ".user.yml")
|
|
141
|
+
|
|
142
|
+
def initialize(self):
|
|
143
|
+
self.do_not_track = False
|
|
144
|
+
|
|
145
|
+
cookie = self.get_cookie()
|
|
146
|
+
self.id = cookie.get("id")
|
|
147
|
+
|
|
148
|
+
subject = Subject()
|
|
149
|
+
subject.set_user_id(self.id)
|
|
150
|
+
tracker.set_subject(subject)
|
|
151
|
+
|
|
152
|
+
def disable_tracking(self):
|
|
153
|
+
self.do_not_track = True
|
|
154
|
+
self.id = None
|
|
155
|
+
self.cookie_dir = None
|
|
156
|
+
tracker.set_subject(None)
|
|
157
|
+
|
|
158
|
+
def set_cookie(self):
|
|
159
|
+
# If the user points dbt to a profile directory which exists AND
|
|
160
|
+
# contains a profiles.yml file, then we can set a cookie. If the
|
|
161
|
+
# specified folder does not exist, or if there is not a profiles.yml
|
|
162
|
+
# file in this folder, then an inconsistent cookie can be used. This
|
|
163
|
+
# will change in every dbt invocation until the user points to a
|
|
164
|
+
# profile dir file which contains a valid profiles.yml file.
|
|
165
|
+
#
|
|
166
|
+
# See: https://github.com/dbt-labs/dbt-core/issues/1645
|
|
167
|
+
|
|
168
|
+
user = {"id": str(uuid.uuid4())}
|
|
169
|
+
|
|
170
|
+
cookie_path = os.path.abspath(self.cookie_dir)
|
|
171
|
+
profiles_file = os.path.join(cookie_path, "profiles.yml")
|
|
172
|
+
if os.path.exists(cookie_path) and os.path.exists(profiles_file):
|
|
173
|
+
with open(self.cookie_path, "w") as fh:
|
|
174
|
+
yaml.dump(user, fh)
|
|
175
|
+
|
|
176
|
+
return user
|
|
177
|
+
|
|
178
|
+
def get_cookie(self):
|
|
179
|
+
if not os.path.isfile(self.cookie_path):
|
|
180
|
+
user = self.set_cookie()
|
|
181
|
+
else:
|
|
182
|
+
with open(self.cookie_path, "r") as fh:
|
|
183
|
+
try:
|
|
184
|
+
user = safe_load(fh)
|
|
185
|
+
if user is None:
|
|
186
|
+
user = self.set_cookie()
|
|
187
|
+
except yaml.reader.ReaderError:
|
|
188
|
+
user = self.set_cookie()
|
|
189
|
+
return user
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
active_user: Optional[User] = None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_platform_context():
|
|
196
|
+
data = {
|
|
197
|
+
"platform": platform.platform(),
|
|
198
|
+
"python": platform.python_version(),
|
|
199
|
+
"python_version": platform.python_implementation(),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return SelfDescribingJson(PLATFORM_SPEC, data)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_dbt_env_context():
|
|
206
|
+
default = "manual"
|
|
207
|
+
|
|
208
|
+
dbt_invocation_env = os.getenv(DBT_INVOCATION_ENV, default)
|
|
209
|
+
if dbt_invocation_env == "":
|
|
210
|
+
dbt_invocation_env = default
|
|
211
|
+
|
|
212
|
+
data = {
|
|
213
|
+
"environment": dbt_invocation_env,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return SelfDescribingJson(INVOCATION_ENV_SPEC, data)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def track(user, *args, **kwargs):
|
|
220
|
+
if user.do_not_track:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
fire_event(SendingEvent(kwargs=str(kwargs)))
|
|
224
|
+
try:
|
|
225
|
+
tracker.track(StructuredEvent(*args, **kwargs))
|
|
226
|
+
except Exception:
|
|
227
|
+
fire_event(SendEventFailure())
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def track_project_id(options):
|
|
231
|
+
assert active_user is not None, "Cannot track project_id when active user is None"
|
|
232
|
+
context = [SelfDescribingJson(PROJECT_ID_SPEC, options)]
|
|
233
|
+
|
|
234
|
+
track(
|
|
235
|
+
active_user,
|
|
236
|
+
category="dbt",
|
|
237
|
+
action="project_id",
|
|
238
|
+
label=get_invocation_id(),
|
|
239
|
+
context=context,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def track_adapter_info(options):
|
|
244
|
+
assert active_user is not None, "Cannot track adapter_info when active user is None"
|
|
245
|
+
context = [SelfDescribingJson(ADAPTER_INFO_SPEC, options)]
|
|
246
|
+
|
|
247
|
+
track(
|
|
248
|
+
active_user,
|
|
249
|
+
category="dbt",
|
|
250
|
+
action="adapter_info",
|
|
251
|
+
label=get_invocation_id(),
|
|
252
|
+
context=context,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def track_invocation_start(invocation_context):
|
|
257
|
+
data = {"progress": "start", "result_type": None, "result": None}
|
|
258
|
+
data.update(invocation_context)
|
|
259
|
+
context = [
|
|
260
|
+
SelfDescribingJson(INVOCATION_SPEC, data),
|
|
261
|
+
get_platform_context(),
|
|
262
|
+
get_dbt_env_context(),
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
track(active_user, category="dbt", action="invocation", label="start", context=context)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def track_project_load(options):
|
|
269
|
+
context = [SelfDescribingJson(LOAD_ALL_TIMING_SPEC, options)]
|
|
270
|
+
assert active_user is not None, "Cannot track project loading time when active user is None"
|
|
271
|
+
|
|
272
|
+
track(
|
|
273
|
+
active_user,
|
|
274
|
+
category="dbt",
|
|
275
|
+
action="load_project",
|
|
276
|
+
label=get_invocation_id(),
|
|
277
|
+
context=context,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def track_resource_counts(resource_counts):
|
|
282
|
+
context = [SelfDescribingJson(RESOURCE_COUNTS, resource_counts)]
|
|
283
|
+
assert active_user is not None, "Cannot track resource counts when active user is None"
|
|
284
|
+
|
|
285
|
+
track(
|
|
286
|
+
active_user,
|
|
287
|
+
category="dbt",
|
|
288
|
+
action="resource_counts",
|
|
289
|
+
label=get_invocation_id(),
|
|
290
|
+
context=context,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def track_model_run(options):
|
|
295
|
+
context = [SelfDescribingJson(RUN_MODEL_SPEC, options)]
|
|
296
|
+
assert active_user is not None, "Cannot track model runs when active user is None"
|
|
297
|
+
|
|
298
|
+
track(
|
|
299
|
+
active_user, category="dbt", action="run_model", label=get_invocation_id(), context=context
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def track_rpc_request(options):
|
|
304
|
+
context = [SelfDescribingJson(RPC_REQUEST_SPEC, options)]
|
|
305
|
+
assert active_user is not None, "Cannot track rpc requests when active user is None"
|
|
306
|
+
|
|
307
|
+
track(
|
|
308
|
+
active_user,
|
|
309
|
+
category="dbt",
|
|
310
|
+
action="rpc_request",
|
|
311
|
+
label=get_invocation_id(),
|
|
312
|
+
context=context,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_base_invocation_context():
|
|
317
|
+
assert (
|
|
318
|
+
active_user is not None
|
|
319
|
+
), "initialize active user before calling get_base_invocation_context"
|
|
320
|
+
return {
|
|
321
|
+
"project_id": None,
|
|
322
|
+
"user_id": active_user.id,
|
|
323
|
+
"invocation_id": active_user.invocation_id,
|
|
324
|
+
"command": None,
|
|
325
|
+
"options": None,
|
|
326
|
+
"version": str(dbt_version.installed),
|
|
327
|
+
"run_type": "regular",
|
|
328
|
+
"adapter_type": None,
|
|
329
|
+
"adapter_unique_id": None,
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def track_package_install(command_name: str, project_hashed_name: Optional[str], options):
|
|
334
|
+
assert active_user is not None, "Cannot track package installs when active user is None"
|
|
335
|
+
|
|
336
|
+
invocation_data = get_base_invocation_context()
|
|
337
|
+
|
|
338
|
+
invocation_data.update({"project_id": project_hashed_name, "command": command_name})
|
|
339
|
+
|
|
340
|
+
context = [
|
|
341
|
+
SelfDescribingJson(INVOCATION_SPEC, invocation_data),
|
|
342
|
+
SelfDescribingJson(PACKAGE_INSTALL_SPEC, options),
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
track(
|
|
346
|
+
active_user,
|
|
347
|
+
category="dbt",
|
|
348
|
+
action="package",
|
|
349
|
+
label=get_invocation_id(),
|
|
350
|
+
property_="install",
|
|
351
|
+
context=context,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def track_deprecation_warn(options):
|
|
356
|
+
|
|
357
|
+
assert active_user is not None, "Cannot track deprecation warnings when active user is None"
|
|
358
|
+
|
|
359
|
+
context = [SelfDescribingJson(DEPRECATION_WARN_SPEC, options)]
|
|
360
|
+
|
|
361
|
+
track(
|
|
362
|
+
active_user,
|
|
363
|
+
category="dbt",
|
|
364
|
+
action="deprecation",
|
|
365
|
+
label=get_invocation_id(),
|
|
366
|
+
property_="warn",
|
|
367
|
+
context=context,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def track_behavior_change_warn(msg: EventMsg) -> None:
|
|
372
|
+
if msg.info.name != "BehaviorChangeEvent" or active_user is None:
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
context = [SelfDescribingJson(BEHAVIOR_CHANGE_WARN_SPEC, msg_to_dict(msg))]
|
|
376
|
+
track(
|
|
377
|
+
active_user,
|
|
378
|
+
category="dbt",
|
|
379
|
+
action=msg.info.name,
|
|
380
|
+
label=get_invocation_id(),
|
|
381
|
+
context=context,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def track_invocation_end(invocation_context, result_type=None):
|
|
386
|
+
data = {"progress": "end", "result_type": result_type, "result": None}
|
|
387
|
+
data.update(invocation_context)
|
|
388
|
+
context = [
|
|
389
|
+
SelfDescribingJson(INVOCATION_SPEC, data),
|
|
390
|
+
get_platform_context(),
|
|
391
|
+
get_dbt_env_context(),
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
assert active_user is not None, "Cannot track invocation end when active user is None"
|
|
395
|
+
|
|
396
|
+
track(active_user, category="dbt", action="invocation", label="end", context=context)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def track_invalid_invocation(args=None, result_type=None):
|
|
400
|
+
assert active_user is not None, "Cannot track invalid invocations when active user is None"
|
|
401
|
+
invocation_context = get_base_invocation_context()
|
|
402
|
+
invocation_context.update({"command": args.which})
|
|
403
|
+
data = {"progress": "invalid", "result_type": result_type, "result": None}
|
|
404
|
+
data.update(invocation_context)
|
|
405
|
+
context = [
|
|
406
|
+
SelfDescribingJson(INVOCATION_SPEC, data),
|
|
407
|
+
get_platform_context(),
|
|
408
|
+
get_dbt_env_context(),
|
|
409
|
+
]
|
|
410
|
+
track(active_user, category="dbt", action="invocation", label="invalid", context=context)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def track_experimental_parser_sample(options):
|
|
414
|
+
context = [SelfDescribingJson(EXPERIMENTAL_PARSER, options)]
|
|
415
|
+
assert (
|
|
416
|
+
active_user is not None
|
|
417
|
+
), "Cannot track experimental parser info when active user is None"
|
|
418
|
+
|
|
419
|
+
track(
|
|
420
|
+
active_user,
|
|
421
|
+
category="dbt",
|
|
422
|
+
action="experimental_parser",
|
|
423
|
+
label=get_invocation_id(),
|
|
424
|
+
context=context,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def track_partial_parser(options):
|
|
429
|
+
context = [SelfDescribingJson(PARTIAL_PARSER, options)]
|
|
430
|
+
assert active_user is not None, "Cannot track partial parser info when active user is None"
|
|
431
|
+
|
|
432
|
+
track(
|
|
433
|
+
active_user,
|
|
434
|
+
category="dbt",
|
|
435
|
+
action="partial_parser",
|
|
436
|
+
label=get_invocation_id(),
|
|
437
|
+
context=context,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def track_plugin_get_nodes(options):
|
|
442
|
+
context = [SelfDescribingJson(PLUGIN_GET_NODES, options)]
|
|
443
|
+
assert active_user is not None, "Cannot track plugin node info when active user is None"
|
|
444
|
+
|
|
445
|
+
track(
|
|
446
|
+
active_user,
|
|
447
|
+
category="dbt",
|
|
448
|
+
action="plugin_get_nodes",
|
|
449
|
+
label=get_invocation_id(),
|
|
450
|
+
context=context,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def track_runnable_timing(options):
|
|
455
|
+
context = [SelfDescribingJson(RUNNABLE_TIMING, options)]
|
|
456
|
+
assert active_user is not None, "Cannot track runnable info when active user is None"
|
|
457
|
+
track(
|
|
458
|
+
active_user,
|
|
459
|
+
category="dbt",
|
|
460
|
+
action="runnable_timing",
|
|
461
|
+
label=get_invocation_id(),
|
|
462
|
+
context=context,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def track_artifact_upload(options):
|
|
467
|
+
context = [SelfDescribingJson(ARTIFACT_UPLOAD, options)]
|
|
468
|
+
assert active_user is not None, "Cannot track artifact upload when active user is None"
|
|
469
|
+
|
|
470
|
+
track(
|
|
471
|
+
active_user,
|
|
472
|
+
category="dbt",
|
|
473
|
+
action="artifact_upload",
|
|
474
|
+
label=get_invocation_id(),
|
|
475
|
+
context=context,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def flush():
|
|
480
|
+
fire_event(FlushEvents())
|
|
481
|
+
try:
|
|
482
|
+
tracker.flush()
|
|
483
|
+
except Exception:
|
|
484
|
+
fire_event(FlushEventsFailure())
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def disable_tracking():
|
|
488
|
+
global active_user
|
|
489
|
+
if active_user is not None:
|
|
490
|
+
active_user.disable_tracking()
|
|
491
|
+
else:
|
|
492
|
+
active_user = User(None)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def do_not_track():
|
|
496
|
+
global active_user
|
|
497
|
+
active_user = User(None)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def initialize_from_flags(send_anonymous_usage_stats, profiles_dir):
|
|
501
|
+
global active_user
|
|
502
|
+
if send_anonymous_usage_stats:
|
|
503
|
+
active_user = User(profiles_dir)
|
|
504
|
+
try:
|
|
505
|
+
active_user.initialize()
|
|
506
|
+
except Exception:
|
|
507
|
+
fire_event(TrackingInitializeFailure(exc_info=traceback.format_exc()))
|
|
508
|
+
active_user = User(None)
|
|
509
|
+
else:
|
|
510
|
+
active_user = User(None)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
@contextmanager
|
|
514
|
+
def track_run(run_command=None):
|
|
515
|
+
invocation_context = get_base_invocation_context()
|
|
516
|
+
invocation_context["command"] = run_command
|
|
517
|
+
|
|
518
|
+
track_invocation_start(invocation_context)
|
|
519
|
+
try:
|
|
520
|
+
yield
|
|
521
|
+
track_invocation_end(invocation_context, result_type="ok")
|
|
522
|
+
except (NotImplementedError, FailedToConnectError) as e:
|
|
523
|
+
fire_event(MainEncounteredError(exc=str(e)))
|
|
524
|
+
track_invocation_end(invocation_context, result_type="error")
|
|
525
|
+
except Exception:
|
|
526
|
+
track_invocation_end(invocation_context, result_type="error")
|
|
527
|
+
raise
|
|
528
|
+
finally:
|
|
529
|
+
flush()
|
dvt/utils/__init__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import uuid
|
|
3
|
+
import zipfile
|
|
4
|
+
|
|
5
|
+
import dvt.tracking
|
|
6
|
+
import requests
|
|
7
|
+
from dvt._pydantic_shim import BaseSettings # type: ignore
|
|
8
|
+
from dvt.config.runtime import UnsetProfile, load_project
|
|
9
|
+
from dvt.constants import MANIFEST_FILE_NAME, RUN_RESULTS_FILE_NAME
|
|
10
|
+
from dvt.events.types import ArtifactUploadSkipped, ArtifactUploadSuccess
|
|
11
|
+
from dvt.exceptions import DbtProjectError
|
|
12
|
+
|
|
13
|
+
from dbt_common.events.functions import fire_event
|
|
14
|
+
from dbt_common.exceptions import DbtBaseException as DbtException
|
|
15
|
+
|
|
16
|
+
MAX_RETRIES = 3
|
|
17
|
+
|
|
18
|
+
EXECUTION_ARTIFACTS = [MANIFEST_FILE_NAME, RUN_RESULTS_FILE_NAME]
|
|
19
|
+
|
|
20
|
+
PRODUCED_ARTIFACTS_PATHS: set[str] = set()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# artifact paths calling this will be uploaded to dbt Cloud
|
|
24
|
+
def add_artifact_produced(artifact_path: str):
|
|
25
|
+
PRODUCED_ARTIFACTS_PATHS.add(artifact_path)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ArtifactUploadConfig(BaseSettings):
|
|
29
|
+
tenant_hostname: str
|
|
30
|
+
DBT_CLOUD_TOKEN: str
|
|
31
|
+
DBT_CLOUD_ACCOUNT_ID: str
|
|
32
|
+
DBT_CLOUD_ENVIRONMENT_ID: str
|
|
33
|
+
|
|
34
|
+
def get_ingest_url(self):
|
|
35
|
+
return f"https://{self.tenant_hostname}/api/private/accounts/{self.DBT_CLOUD_ACCOUNT_ID}/environments/{self.DBT_CLOUD_ENVIRONMENT_ID}/ingests/"
|
|
36
|
+
|
|
37
|
+
def get_complete_url(self, ingest_id):
|
|
38
|
+
return f"{self.get_ingest_url()}{ingest_id}/"
|
|
39
|
+
|
|
40
|
+
def get_headers(self, invocation_id=None):
|
|
41
|
+
if invocation_id is None:
|
|
42
|
+
invocation_id = str(uuid.uuid4())
|
|
43
|
+
return {
|
|
44
|
+
"Accept": "application/json",
|
|
45
|
+
"X-Invocation-Id": invocation_id,
|
|
46
|
+
"Authorization": f"Token {self.DBT_CLOUD_TOKEN}",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _retry_with_backoff(operation_name, func, max_retries=MAX_RETRIES, retry_codes=None):
|
|
51
|
+
"""Execute a function with exponential backoff retry logic.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
operation_name: Name of the operation for error messages
|
|
55
|
+
func: Function to execute that returns (success, result)
|
|
56
|
+
max_retries: Maximum number of retry attempts
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The result from the function if successful
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
DbtException: If all retry attempts fail
|
|
63
|
+
"""
|
|
64
|
+
if retry_codes is None:
|
|
65
|
+
retry_codes = [500, 502, 503, 504]
|
|
66
|
+
retry_delay = 1
|
|
67
|
+
for attempt in range(max_retries + 1):
|
|
68
|
+
try:
|
|
69
|
+
success, result = func()
|
|
70
|
+
if success:
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
if result.status_code not in retry_codes:
|
|
74
|
+
raise DbtException(f"Error {operation_name}: {result}")
|
|
75
|
+
if attempt == max_retries: # Last attempt
|
|
76
|
+
raise DbtException(f"Error {operation_name}: {result}")
|
|
77
|
+
except requests.RequestException as e:
|
|
78
|
+
if attempt == max_retries: # Last attempt
|
|
79
|
+
raise DbtException(f"Error {operation_name}: {str(e)}")
|
|
80
|
+
|
|
81
|
+
time.sleep(retry_delay)
|
|
82
|
+
retry_delay *= 2 # exponential backoff
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def upload_artifacts(project_dir, target_path, command):
|
|
86
|
+
# Check if there are artifacts to upload for this command
|
|
87
|
+
if not PRODUCED_ARTIFACTS_PATHS:
|
|
88
|
+
fire_event(ArtifactUploadSkipped(msg="No artifacts to upload for current command"))
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# read configurations
|
|
92
|
+
try:
|
|
93
|
+
project = load_project(
|
|
94
|
+
project_dir, version_check=False, profile=UnsetProfile(), cli_vars=None
|
|
95
|
+
)
|
|
96
|
+
if not project.dbt_cloud or "tenant_hostname" not in project.dbt_cloud:
|
|
97
|
+
raise DbtProjectError("dbt_cloud.tenant_hostname not found in dbt_project.yml")
|
|
98
|
+
tenant_hostname = project.dbt_cloud["tenant_hostname"]
|
|
99
|
+
if not tenant_hostname:
|
|
100
|
+
raise DbtProjectError("dbt_cloud.tenant_hostname is empty in dbt_project.yml")
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise DbtProjectError(
|
|
103
|
+
f"Error reading dbt_cloud.tenant_hostname from dbt_project.yml: {str(e)}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
config = ArtifactUploadConfig(tenant_hostname=tenant_hostname)
|
|
107
|
+
|
|
108
|
+
if not target_path:
|
|
109
|
+
target_path = "target"
|
|
110
|
+
|
|
111
|
+
# Create zip file with artifacts
|
|
112
|
+
zip_file_name = "target.zip"
|
|
113
|
+
with zipfile.ZipFile(zip_file_name, "w") as z:
|
|
114
|
+
for artifact_path in PRODUCED_ARTIFACTS_PATHS:
|
|
115
|
+
z.write(artifact_path, artifact_path.split("/")[-1])
|
|
116
|
+
|
|
117
|
+
# Step 1: Create ingest request with retry
|
|
118
|
+
def create_ingest():
|
|
119
|
+
response = requests.post(url=config.get_ingest_url(), headers=config.get_headers())
|
|
120
|
+
return response.status_code == 200, response
|
|
121
|
+
|
|
122
|
+
response = _retry_with_backoff("creating ingest request", create_ingest)
|
|
123
|
+
response_data = response.json()
|
|
124
|
+
ingest_id = response_data["data"]["id"]
|
|
125
|
+
upload_url = response_data["data"]["upload_url"]
|
|
126
|
+
|
|
127
|
+
# Step 2: Upload the zip file to the provided URL with retry
|
|
128
|
+
with open(zip_file_name, "rb") as f:
|
|
129
|
+
file_data = f.read()
|
|
130
|
+
|
|
131
|
+
def upload_file():
|
|
132
|
+
upload_response = requests.put(url=upload_url, data=file_data)
|
|
133
|
+
return upload_response.status_code in (200, 204), upload_response
|
|
134
|
+
|
|
135
|
+
_retry_with_backoff("uploading artifacts", upload_file)
|
|
136
|
+
|
|
137
|
+
# Step 3: Mark the ingest as successful with retry
|
|
138
|
+
def complete_ingest():
|
|
139
|
+
complete_response = requests.patch(
|
|
140
|
+
url=config.get_complete_url(ingest_id),
|
|
141
|
+
headers=config.get_headers(),
|
|
142
|
+
json={"upload_status": "SUCCESS"},
|
|
143
|
+
)
|
|
144
|
+
return complete_response.status_code == 204, complete_response
|
|
145
|
+
|
|
146
|
+
_retry_with_backoff("completing ingest", complete_ingest)
|
|
147
|
+
|
|
148
|
+
fire_event(ArtifactUploadSuccess(msg=f"command {command} completed successfully"))
|
|
149
|
+
if dbt.tracking.active_user is not None:
|
|
150
|
+
dbt.tracking.track_artifact_upload({"command": command})
|
|
151
|
+
PRODUCED_ARTIFACTS_PATHS.clear()
|