dagster-dbt 0.23.3__py3-none-any.whl → 0.28.4__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.
- dagster_dbt/__init__.py +41 -140
- dagster_dbt/asset_decorator.py +49 -230
- dagster_dbt/asset_specs.py +65 -0
- dagster_dbt/asset_utils.py +655 -338
- dagster_dbt/cli/app.py +44 -43
- dagster_dbt/cloud/__init__.py +6 -4
- dagster_dbt/cloud/asset_defs.py +119 -177
- dagster_dbt/cloud/cli.py +3 -4
- dagster_dbt/cloud/ops.py +9 -6
- dagster_dbt/cloud/resources.py +9 -4
- dagster_dbt/cloud/types.py +12 -7
- dagster_dbt/cloud/utils.py +186 -0
- dagster_dbt/cloud_v2/__init__.py +10 -0
- dagster_dbt/cloud_v2/asset_decorator.py +81 -0
- dagster_dbt/cloud_v2/cli_invocation.py +67 -0
- dagster_dbt/cloud_v2/client.py +438 -0
- dagster_dbt/cloud_v2/resources.py +462 -0
- dagster_dbt/cloud_v2/run_handler.py +229 -0
- dagster_dbt/cloud_v2/sensor_builder.py +254 -0
- dagster_dbt/cloud_v2/types.py +143 -0
- dagster_dbt/compat.py +107 -0
- dagster_dbt/components/__init__.py +0 -0
- dagster_dbt/components/dbt_project/__init__.py +0 -0
- dagster_dbt/components/dbt_project/component.py +545 -0
- dagster_dbt/components/dbt_project/scaffolder.py +65 -0
- dagster_dbt/core/__init__.py +0 -10
- dagster_dbt/core/dbt_cli_event.py +612 -0
- dagster_dbt/core/dbt_cli_invocation.py +474 -0
- dagster_dbt/core/dbt_event_iterator.py +399 -0
- dagster_dbt/core/resource.py +733 -0
- dagster_dbt/core/utils.py +14 -279
- dagster_dbt/dagster_dbt_translator.py +317 -74
- dagster_dbt/dbt_core_version.py +1 -0
- dagster_dbt/dbt_manifest.py +6 -5
- dagster_dbt/dbt_manifest_asset_selection.py +62 -22
- dagster_dbt/dbt_project.py +179 -40
- dagster_dbt/dbt_project_manager.py +173 -0
- dagster_dbt/dbt_version.py +0 -0
- dagster_dbt/errors.py +9 -84
- dagster_dbt/freshness_builder.py +147 -0
- dagster_dbt/include/pyproject.toml.jinja +21 -0
- dagster_dbt/include/scaffold/assets.py.jinja +1 -8
- dagster_dbt/include/scaffold/definitions.py.jinja +0 -15
- dagster_dbt/include/scaffold/project.py.jinja +1 -0
- dagster_dbt/include/setup.py.jinja +2 -3
- dagster_dbt/metadata_set.py +18 -0
- dagster_dbt/utils.py +136 -234
- dagster_dbt/version.py +1 -1
- dagster_dbt-0.28.4.dist-info/METADATA +47 -0
- dagster_dbt-0.28.4.dist-info/RECORD +59 -0
- {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info}/WHEEL +1 -1
- {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info}/entry_points.txt +3 -0
- {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info/licenses}/LICENSE +1 -1
- dagster_dbt/asset_defs.py +0 -1049
- dagster_dbt/core/resources.py +0 -527
- dagster_dbt/core/resources_v2.py +0 -1542
- dagster_dbt/core/types.py +0 -63
- dagster_dbt/dbt_resource.py +0 -220
- dagster_dbt/include/scaffold/constants.py.jinja +0 -21
- dagster_dbt/ops.py +0 -134
- dagster_dbt/types.py +0 -22
- dagster_dbt-0.23.3.dist-info/METADATA +0 -31
- dagster_dbt-0.23.3.dist-info/RECORD +0 -43
- {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info}/top_level.txt +0 -0
dagster_dbt/core/utils.py
CHANGED
|
@@ -1,284 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import subprocess
|
|
4
|
-
from typing import Any, Iterator, List, Mapping, NamedTuple, Optional, Sequence, Union
|
|
1
|
+
from concurrent.futures import Future
|
|
2
|
+
from typing import Any, Union
|
|
5
3
|
|
|
6
|
-
import dagster._check as check
|
|
7
|
-
from dagster._core.utils import coerce_valid_log_level
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
DEFAULT_DBT_TARGET_PATH = "target"
|
|
17
|
-
|
|
18
|
-
DBT_RUN_RESULTS_COMMANDS = ["run", "test", "seed", "snapshot", "docs generate", "build"]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class DbtCliEvent(NamedTuple):
|
|
22
|
-
"""Helper class to encapsulate parsed information from an active dbt CLI process."""
|
|
23
|
-
|
|
24
|
-
line: Optional[str]
|
|
25
|
-
message: Optional[str]
|
|
26
|
-
parsed_json_line: Optional[Mapping[str, Any]]
|
|
27
|
-
log_level: Optional[int]
|
|
28
|
-
|
|
29
|
-
@classmethod
|
|
30
|
-
def from_line(cls, line: str, json_log_format: bool) -> "DbtCliEvent":
|
|
31
|
-
message = line
|
|
32
|
-
parsed_json_line = None
|
|
33
|
-
log_level = "info"
|
|
34
|
-
|
|
35
|
-
# parse attributes out of json fields
|
|
36
|
-
if json_log_format:
|
|
37
|
-
try:
|
|
38
|
-
parsed_json_line = json.loads(line)
|
|
39
|
-
except json.JSONDecodeError:
|
|
40
|
-
pass
|
|
41
|
-
else:
|
|
42
|
-
# in rare cases, the loaded json line may be a string rather than a dictionary
|
|
43
|
-
if isinstance(parsed_json_line, dict):
|
|
44
|
-
message = parsed_json_line.get("info", {}).get(
|
|
45
|
-
"msg",
|
|
46
|
-
# If all else fails, default to the whole line
|
|
47
|
-
line,
|
|
48
|
-
)
|
|
49
|
-
log_level = parsed_json_line.get("info", {}).get(
|
|
50
|
-
"level",
|
|
51
|
-
# If all else fails, default to the `debug` level
|
|
52
|
-
"debug",
|
|
53
|
-
)
|
|
54
|
-
# attempt to parse log level out of raw line
|
|
55
|
-
elif "Done." not in line:
|
|
56
|
-
# attempt to parse a log level out of the line
|
|
57
|
-
if "ERROR" in line:
|
|
58
|
-
log_level = "error"
|
|
59
|
-
elif "WARN" in line:
|
|
60
|
-
log_level = "warn"
|
|
61
|
-
|
|
62
|
-
return DbtCliEvent(
|
|
63
|
-
line=line,
|
|
64
|
-
message=message,
|
|
65
|
-
parsed_json_line=parsed_json_line,
|
|
66
|
-
log_level=coerce_valid_log_level(log_level),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _create_command_list(
|
|
71
|
-
executable: str,
|
|
72
|
-
warn_error: bool,
|
|
73
|
-
json_log_format: bool,
|
|
74
|
-
command: str,
|
|
75
|
-
flags_dict: Mapping[str, Any],
|
|
76
|
-
debug: bool,
|
|
77
|
-
) -> Sequence[str]:
|
|
78
|
-
prefix = [executable]
|
|
79
|
-
if warn_error:
|
|
80
|
-
prefix += ["--warn-error"]
|
|
81
|
-
if json_log_format:
|
|
82
|
-
prefix += ["--no-use-colors", "--log-format", "json"]
|
|
83
|
-
if debug:
|
|
84
|
-
prefix += ["--debug"]
|
|
85
|
-
|
|
86
|
-
full_command = [*command.split(" "), *build_command_args_from_flags(flags_dict)]
|
|
87
|
-
|
|
88
|
-
return prefix + full_command
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def build_command_args_from_flags(flags_dict: Mapping[str, Any]) -> Sequence[str]:
|
|
92
|
-
result = []
|
|
93
|
-
for flag, value in flags_dict.items():
|
|
94
|
-
if not value:
|
|
5
|
+
def get_future_completion_state_or_err(futures: list[Union[Future, Any]]) -> bool:
|
|
6
|
+
"""Given a list of futures (and potentially other objects), return True if all futures are completed.
|
|
7
|
+
If any future has an exception, raise the exception.
|
|
8
|
+
"""
|
|
9
|
+
for future in futures:
|
|
10
|
+
if not isinstance(future, Future):
|
|
95
11
|
continue
|
|
96
12
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if isinstance(value, bool):
|
|
100
|
-
pass
|
|
101
|
-
elif isinstance(value, list):
|
|
102
|
-
check.list_param(value, f"config.{flag}", of_type=str)
|
|
103
|
-
result += value
|
|
104
|
-
elif isinstance(value, dict):
|
|
105
|
-
result.append(json.dumps(value))
|
|
106
|
-
else:
|
|
107
|
-
result.append(str(value))
|
|
108
|
-
|
|
109
|
-
return result
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _core_execute_cli(
|
|
113
|
-
command_list: Sequence[str],
|
|
114
|
-
ignore_handled_error: bool,
|
|
115
|
-
json_log_format: bool,
|
|
116
|
-
project_dir: str,
|
|
117
|
-
) -> Iterator[Union[DbtCliEvent, int]]:
|
|
118
|
-
"""Runs a dbt command in a subprocess and yields parsed output line by line."""
|
|
119
|
-
# Execute the dbt CLI command in a subprocess.
|
|
120
|
-
messages: List[str] = []
|
|
121
|
-
|
|
122
|
-
# run dbt with unbuffered output
|
|
123
|
-
passenv = os.environ.copy()
|
|
124
|
-
passenv["PYTHONUNBUFFERED"] = "1"
|
|
125
|
-
process = subprocess.Popen(
|
|
126
|
-
command_list,
|
|
127
|
-
stdout=subprocess.PIPE,
|
|
128
|
-
stderr=subprocess.STDOUT,
|
|
129
|
-
env=passenv,
|
|
130
|
-
cwd=project_dir if os.path.exists(project_dir) else None,
|
|
131
|
-
)
|
|
132
|
-
for raw_line in process.stdout or []:
|
|
133
|
-
line = raw_line.decode().strip()
|
|
134
|
-
|
|
135
|
-
cli_event = DbtCliEvent.from_line(line, json_log_format)
|
|
136
|
-
|
|
137
|
-
if cli_event.message is not None:
|
|
138
|
-
messages.append(cli_event.message)
|
|
139
|
-
|
|
140
|
-
# yield the parsed values
|
|
141
|
-
yield cli_event
|
|
142
|
-
|
|
143
|
-
process.wait()
|
|
144
|
-
return_code = process.returncode
|
|
145
|
-
|
|
146
|
-
if return_code == 2:
|
|
147
|
-
raise DagsterDbtCliFatalRuntimeError(messages=messages)
|
|
148
|
-
|
|
149
|
-
if return_code == 1 and not ignore_handled_error:
|
|
150
|
-
raise DagsterDbtCliHandledRuntimeError(messages=messages)
|
|
151
|
-
|
|
152
|
-
yield return_code
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def execute_cli_stream(
|
|
156
|
-
executable: str,
|
|
157
|
-
command: str,
|
|
158
|
-
flags_dict: Mapping[str, Any],
|
|
159
|
-
log: Any,
|
|
160
|
-
warn_error: bool,
|
|
161
|
-
ignore_handled_error: bool,
|
|
162
|
-
json_log_format: bool = True,
|
|
163
|
-
capture_logs: bool = True,
|
|
164
|
-
debug: bool = False,
|
|
165
|
-
) -> Iterator[DbtCliEvent]:
|
|
166
|
-
"""Executes a command on the dbt CLI in a subprocess."""
|
|
167
|
-
command_list = _create_command_list(
|
|
168
|
-
executable=executable,
|
|
169
|
-
warn_error=warn_error,
|
|
170
|
-
json_log_format=json_log_format,
|
|
171
|
-
command=command,
|
|
172
|
-
flags_dict=flags_dict,
|
|
173
|
-
debug=debug,
|
|
174
|
-
)
|
|
175
|
-
log.info(f"Executing command: {' '.join(command_list)}")
|
|
176
|
-
|
|
177
|
-
for event in _core_execute_cli(
|
|
178
|
-
command_list=command_list,
|
|
179
|
-
json_log_format=json_log_format,
|
|
180
|
-
ignore_handled_error=ignore_handled_error,
|
|
181
|
-
project_dir=flags_dict["project-dir"],
|
|
182
|
-
):
|
|
183
|
-
if isinstance(event, int):
|
|
184
|
-
return_code = event
|
|
185
|
-
log.info(f"dbt exited with return code {return_code}")
|
|
186
|
-
break
|
|
187
|
-
|
|
188
|
-
yield event
|
|
189
|
-
if capture_logs:
|
|
190
|
-
log.log(event.log_level, event.message)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def execute_cli(
|
|
194
|
-
executable: str,
|
|
195
|
-
command: str,
|
|
196
|
-
flags_dict: Mapping[str, Any],
|
|
197
|
-
log: Any,
|
|
198
|
-
warn_error: bool,
|
|
199
|
-
ignore_handled_error: bool,
|
|
200
|
-
target_path: str,
|
|
201
|
-
docs_url: Optional[str] = None,
|
|
202
|
-
json_log_format: bool = True,
|
|
203
|
-
capture_logs: bool = True,
|
|
204
|
-
debug: bool = False,
|
|
205
|
-
) -> DbtCliOutput:
|
|
206
|
-
"""Executes a command on the dbt CLI in a subprocess."""
|
|
207
|
-
check.str_param(executable, "executable")
|
|
208
|
-
check.str_param(command, "command")
|
|
209
|
-
check.mapping_param(flags_dict, "flags_dict", key_type=str)
|
|
210
|
-
check.bool_param(warn_error, "warn_error")
|
|
211
|
-
check.bool_param(ignore_handled_error, "ignore_handled_error")
|
|
212
|
-
|
|
213
|
-
command_list = _create_command_list(
|
|
214
|
-
executable=executable,
|
|
215
|
-
warn_error=warn_error,
|
|
216
|
-
json_log_format=json_log_format,
|
|
217
|
-
command=command,
|
|
218
|
-
flags_dict=flags_dict,
|
|
219
|
-
debug=debug,
|
|
220
|
-
)
|
|
221
|
-
log.info(f"Executing command: {' '.join(command_list)}")
|
|
222
|
-
|
|
223
|
-
return_code = 0
|
|
224
|
-
lines, parsed_json_lines = [], []
|
|
225
|
-
for event in _core_execute_cli(
|
|
226
|
-
command_list=command_list,
|
|
227
|
-
json_log_format=json_log_format,
|
|
228
|
-
ignore_handled_error=ignore_handled_error,
|
|
229
|
-
project_dir=flags_dict["project-dir"],
|
|
230
|
-
):
|
|
231
|
-
if isinstance(event, int):
|
|
232
|
-
return_code = event
|
|
233
|
-
log.info(f"dbt exited with return code {return_code}")
|
|
234
|
-
break
|
|
235
|
-
|
|
236
|
-
if event.line is not None:
|
|
237
|
-
lines.append(event.line)
|
|
238
|
-
if event.parsed_json_line is not None:
|
|
239
|
-
parsed_json_lines.append(event.parsed_json_line)
|
|
240
|
-
|
|
241
|
-
if capture_logs:
|
|
242
|
-
log.log(event.log_level, event.message)
|
|
243
|
-
|
|
244
|
-
run_results = (
|
|
245
|
-
parse_run_results(flags_dict["project-dir"], target_path)
|
|
246
|
-
if command in DBT_RUN_RESULTS_COMMANDS
|
|
247
|
-
else {}
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
return DbtCliOutput(
|
|
251
|
-
command=" ".join(command_list),
|
|
252
|
-
return_code=return_code,
|
|
253
|
-
raw_output="\n\n".join(lines),
|
|
254
|
-
logs=parsed_json_lines,
|
|
255
|
-
result=run_results,
|
|
256
|
-
docs_url=docs_url,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def parse_run_results(path: str, target_path: str = DEFAULT_DBT_TARGET_PATH) -> Mapping[str, Any]:
|
|
261
|
-
"""Parses the `target/run_results.json` artifact that is produced by a dbt process."""
|
|
262
|
-
run_results_path = os.path.join(path, target_path, "run_results.json")
|
|
263
|
-
try:
|
|
264
|
-
with open(run_results_path, encoding="utf8") as file:
|
|
265
|
-
return json.load(file)
|
|
266
|
-
except FileNotFoundError:
|
|
267
|
-
raise DagsterDbtCliOutputsNotFoundError(path=run_results_path)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def remove_run_results(path: str, target_path: str = DEFAULT_DBT_TARGET_PATH):
|
|
271
|
-
"""Parses the `target/run_results.json` artifact that is produced by a dbt process."""
|
|
272
|
-
run_results_path = os.path.join(path, target_path, "run_results.json")
|
|
273
|
-
if os.path.exists(run_results_path):
|
|
274
|
-
os.remove(run_results_path)
|
|
275
|
-
|
|
13
|
+
if not future.done():
|
|
14
|
+
return False
|
|
276
15
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
with open(manifest_path, encoding="utf8") as file:
|
|
282
|
-
return json.load(file)
|
|
283
|
-
except FileNotFoundError:
|
|
284
|
-
raise DagsterDbtCliOutputsNotFoundError(path=manifest_path)
|
|
16
|
+
exception = future.exception()
|
|
17
|
+
if exception:
|
|
18
|
+
raise exception
|
|
19
|
+
return True
|