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.
Files changed (64) hide show
  1. dagster_dbt/__init__.py +41 -140
  2. dagster_dbt/asset_decorator.py +49 -230
  3. dagster_dbt/asset_specs.py +65 -0
  4. dagster_dbt/asset_utils.py +655 -338
  5. dagster_dbt/cli/app.py +44 -43
  6. dagster_dbt/cloud/__init__.py +6 -4
  7. dagster_dbt/cloud/asset_defs.py +119 -177
  8. dagster_dbt/cloud/cli.py +3 -4
  9. dagster_dbt/cloud/ops.py +9 -6
  10. dagster_dbt/cloud/resources.py +9 -4
  11. dagster_dbt/cloud/types.py +12 -7
  12. dagster_dbt/cloud/utils.py +186 -0
  13. dagster_dbt/cloud_v2/__init__.py +10 -0
  14. dagster_dbt/cloud_v2/asset_decorator.py +81 -0
  15. dagster_dbt/cloud_v2/cli_invocation.py +67 -0
  16. dagster_dbt/cloud_v2/client.py +438 -0
  17. dagster_dbt/cloud_v2/resources.py +462 -0
  18. dagster_dbt/cloud_v2/run_handler.py +229 -0
  19. dagster_dbt/cloud_v2/sensor_builder.py +254 -0
  20. dagster_dbt/cloud_v2/types.py +143 -0
  21. dagster_dbt/compat.py +107 -0
  22. dagster_dbt/components/__init__.py +0 -0
  23. dagster_dbt/components/dbt_project/__init__.py +0 -0
  24. dagster_dbt/components/dbt_project/component.py +545 -0
  25. dagster_dbt/components/dbt_project/scaffolder.py +65 -0
  26. dagster_dbt/core/__init__.py +0 -10
  27. dagster_dbt/core/dbt_cli_event.py +612 -0
  28. dagster_dbt/core/dbt_cli_invocation.py +474 -0
  29. dagster_dbt/core/dbt_event_iterator.py +399 -0
  30. dagster_dbt/core/resource.py +733 -0
  31. dagster_dbt/core/utils.py +14 -279
  32. dagster_dbt/dagster_dbt_translator.py +317 -74
  33. dagster_dbt/dbt_core_version.py +1 -0
  34. dagster_dbt/dbt_manifest.py +6 -5
  35. dagster_dbt/dbt_manifest_asset_selection.py +62 -22
  36. dagster_dbt/dbt_project.py +179 -40
  37. dagster_dbt/dbt_project_manager.py +173 -0
  38. dagster_dbt/dbt_version.py +0 -0
  39. dagster_dbt/errors.py +9 -84
  40. dagster_dbt/freshness_builder.py +147 -0
  41. dagster_dbt/include/pyproject.toml.jinja +21 -0
  42. dagster_dbt/include/scaffold/assets.py.jinja +1 -8
  43. dagster_dbt/include/scaffold/definitions.py.jinja +0 -15
  44. dagster_dbt/include/scaffold/project.py.jinja +1 -0
  45. dagster_dbt/include/setup.py.jinja +2 -3
  46. dagster_dbt/metadata_set.py +18 -0
  47. dagster_dbt/utils.py +136 -234
  48. dagster_dbt/version.py +1 -1
  49. dagster_dbt-0.28.4.dist-info/METADATA +47 -0
  50. dagster_dbt-0.28.4.dist-info/RECORD +59 -0
  51. {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info}/WHEEL +1 -1
  52. {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info}/entry_points.txt +3 -0
  53. {dagster_dbt-0.23.3.dist-info → dagster_dbt-0.28.4.dist-info/licenses}/LICENSE +1 -1
  54. dagster_dbt/asset_defs.py +0 -1049
  55. dagster_dbt/core/resources.py +0 -527
  56. dagster_dbt/core/resources_v2.py +0 -1542
  57. dagster_dbt/core/types.py +0 -63
  58. dagster_dbt/dbt_resource.py +0 -220
  59. dagster_dbt/include/scaffold/constants.py.jinja +0 -21
  60. dagster_dbt/ops.py +0 -134
  61. dagster_dbt/types.py +0 -22
  62. dagster_dbt-0.23.3.dist-info/METADATA +0 -31
  63. dagster_dbt-0.23.3.dist-info/RECORD +0 -43
  64. {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 json
2
- import os
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
- from ..errors import (
10
- DagsterDbtCliFatalRuntimeError,
11
- DagsterDbtCliHandledRuntimeError,
12
- DagsterDbtCliOutputsNotFoundError,
13
- )
14
- from .types import DbtCliOutput
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
- result.append(f"--{flag}")
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
- def parse_manifest(path: str, target_path: str = DEFAULT_DBT_TARGET_PATH) -> Mapping[str, Any]:
278
- """Parses the `target/manifest.json` artifact that is produced by a dbt process."""
279
- manifest_path = os.path.join(path, target_path, "manifest.json")
280
- try:
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