airflow-dbt-python 2.0.1__py3-none-any.whl → 2.2.0__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.
@@ -1,2 +1,3 @@
1
1
  """Provides an Airflow operator and hooks to run all or most dbtcommands."""
2
+
2
3
  from .__version__ import __author__, __copyright__, __title__, __version__
@@ -1,5 +1,6 @@
1
1
  """The module's version information."""
2
+
2
3
  __author__ = "Tomás Farías Santana"
3
4
  __copyright__ = "Copyright 2021 Tomás Farías Santana"
4
5
  __title__ = "airflow-dbt-python"
5
- __version__ = "2.0.1"
6
+ __version__ = "2.2.0"
@@ -1,4 +1,5 @@
1
1
  """Provides a hook to interact with a dbt project."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import json
@@ -24,14 +25,11 @@ from airflow.exceptions import AirflowException
24
25
  from airflow.hooks.base import BaseHook
25
26
  from airflow.models.connection import Connection
26
27
 
27
- from airflow_dbt_python.utils.version import DBT_INSTALLED_LESS_THAN_1_5
28
-
29
28
  if sys.version_info >= (3, 11):
30
29
  from contextlib import chdir as chdir_ctx
31
30
  else:
32
31
  from contextlib_chdir import chdir as chdir_ctx
33
32
 
34
-
35
33
  if TYPE_CHECKING:
36
34
  from dbt.contracts.results import RunResult
37
35
  from dbt.task.base import BaseTask
@@ -227,18 +225,11 @@ class DbtHook(BaseHook):
227
225
  A tuple containing a boolean indicating success and optionally the results
228
226
  of running the dbt command.
229
227
  """
230
- from dbt.adapters.factory import register_adapter
228
+ from dbt.adapters.factory import adapter_management
231
229
  from dbt.task.base import get_nearest_project_dir
232
230
  from dbt.task.clean import CleanTask
233
231
  from dbt.task.deps import DepsTask
234
-
235
- from airflow_dbt_python.utils.version import DBT_INSTALLED_LESS_THAN_1_5
236
-
237
- if DBT_INSTALLED_LESS_THAN_1_5:
238
- from dbt.main import adapter_management, track_run # type: ignore
239
- else:
240
- from dbt.adapters.factory import adapter_management
241
- from dbt.tracking import track_run
232
+ from dbt.tracking import track_run
242
233
 
243
234
  config = self.get_dbt_task_config(command, **kwargs)
244
235
  extra_target = self.get_dbt_target_from_connection(config.target)
@@ -252,36 +243,24 @@ class DbtHook(BaseHook):
252
243
  ) as dbt_dir:
253
244
  # When creating tasks via from_args, dbt switches to the project directory.
254
245
  # We have to do that here as we are not using from_args.
255
- if DBT_INSTALLED_LESS_THAN_1_5:
256
- # For compatibility with older versions of dbt, as the signature
257
- # of move_to_nearest_project_dir changed in dbt-core 1.5 to take
258
- # just the project_dir.
259
- nearest_project_dir = get_nearest_project_dir(config) # type: ignore
260
- else:
261
- nearest_project_dir = get_nearest_project_dir(config.project_dir)
246
+ nearest_project_dir = get_nearest_project_dir(config.project_dir)
262
247
 
263
248
  with chdir_ctx(nearest_project_dir):
264
- config.dbt_task.pre_init_hook(config)
265
249
  self.ensure_profiles(config)
266
250
 
267
- task, runtime_config = config.create_dbt_task(
268
- extra_target, write_perf_info
269
- )
270
- requires_profile = isinstance(task, (CleanTask, DepsTask))
271
-
272
- self.setup_dbt_logging(task, config.debug)
251
+ with adapter_management():
252
+ task, runtime_config = config.create_dbt_task(
253
+ extra_target, write_perf_info
254
+ )
255
+ requires_profile = isinstance(task, (CleanTask, DepsTask))
273
256
 
274
- if runtime_config is not None and not requires_profile:
275
- # The deps command installs the dependencies, which means they may
276
- # not exist before deps runs and the following would raise a
277
- # CompilationError.
278
- runtime_config.load_dependencies()
257
+ self.setup_dbt_logging(task, config.debug)
279
258
 
280
- results = None
281
- with adapter_management():
282
- if not requires_profile:
283
- if runtime_config is not None:
284
- register_adapter(runtime_config)
259
+ if runtime_config is not None and not requires_profile:
260
+ # The deps command installs the dependencies, which means they
261
+ # may not exist before deps runs and the following would raise a
262
+ # CompilationError.
263
+ runtime_config.load_dependencies()
285
264
 
286
265
  with track_run(task):
287
266
  results = task.run()
@@ -419,19 +398,11 @@ class DbtHook(BaseHook):
419
398
  default_stdout. As these are initialized by the CLI app, we need to
420
399
  initialize them here.
421
400
  """
422
- from dbt.events.functions import setup_event_logger
423
-
424
- log_path = None
425
- if task.config is not None:
426
- log_path = getattr(task.config, "log_path", None)
427
-
428
- if DBT_INSTALLED_LESS_THAN_1_5:
429
- setup_event_logger(log_path or "logs")
430
- else:
431
- from dbt.flags import get_flags
401
+ from dbt.events.logging import setup_event_logger
402
+ from dbt.flags import get_flags
432
403
 
433
- flags = get_flags()
434
- setup_event_logger(flags)
404
+ flags = get_flags()
405
+ setup_event_logger(flags)
435
406
 
436
407
  configured_file = logging.getLogger("configured_file")
437
408
  file_log = logging.getLogger("file_log")
@@ -458,7 +429,7 @@ class DbtHook(BaseHook):
458
429
  if not profiles_path.exists():
459
430
  profiles_path.parent.mkdir(exist_ok=True)
460
431
  with profiles_path.open("w", encoding="utf-8") as f:
461
- f.write("config:\n send_anonymous_usage_stats: false\n")
432
+ f.write("flags:\n send_anonymous_usage_stats: false\n")
462
433
 
463
434
  def get_dbt_target_from_connection(
464
435
  self, target: Optional[str]
@@ -1,4 +1,5 @@
1
1
  """A concrete DbtRemoteHook for git repositories with dulwich."""
2
+
2
3
  import datetime as dt
3
4
  from typing import Callable, Optional, Tuple, Union
4
5
 
@@ -90,7 +91,7 @@ class DbtGitRemoteHook(SSHHook, DbtRemoteHook):
90
91
 
91
92
  repo.stage(str(f.relative_to(source)))
92
93
 
93
- ts = dt.datetime.utcnow()
94
+ ts = dt.datetime.now(dt.timezone.utc)
94
95
  repo.do_commit(
95
96
  self.commit_msg.format(ts=ts).encode(), self.commit_author.encode()
96
97
  )
@@ -2,6 +2,7 @@
2
2
 
3
3
  Intended to be used only when running Airflow with a LocalExceutor.
4
4
  """
5
+
5
6
  from __future__ import annotations
6
7
 
7
8
  import shutil
@@ -4,6 +4,7 @@ Internally, DbtRemoteHooks can use Airflow hooks to execute the actual operation
4
4
 
5
5
  Currently, only AWS S3 and the local filesystem are supported as remotes.
6
6
  """
7
+
7
8
  from abc import ABC, abstractmethod
8
9
  from pathlib import Path
9
10
  from typing import Optional, Type
@@ -1,4 +1,5 @@
1
1
  """An implementation for an S3 remote for dbt."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Iterable, Optional
@@ -1,4 +1,5 @@
1
1
  """Airflow operators for all dbt commands."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import datetime as dt
@@ -11,11 +12,13 @@ from airflow.exceptions import AirflowException
11
12
  from airflow.models.baseoperator import BaseOperator
12
13
  from airflow.models.xcom import XCOM_RETURN_KEY
13
14
 
15
+ from airflow_dbt_python.utils.enums import LogFormat
16
+
14
17
  if TYPE_CHECKING:
15
18
  from dbt.contracts.results import RunResult
16
19
 
17
20
  from airflow_dbt_python.hooks.dbt import DbtHook, DbtTaskResult
18
- from airflow_dbt_python.utils.enums import LogFormat, Output
21
+ from airflow_dbt_python.utils.enums import Output
19
22
 
20
23
  base_template_fields = [
21
24
  "project_dir",
@@ -73,7 +76,7 @@ class DbtBaseOperator(BaseOperator):
73
76
  log_path: Optional[str] = None,
74
77
  log_level: Optional[str] = None,
75
78
  log_level_file: Optional[str] = None,
76
- log_format: Optional[LogFormat] = None,
79
+ log_format: LogFormat = LogFormat.DEFAULT,
77
80
  log_cache_events: Optional[bool] = False,
78
81
  quiet: Optional[bool] = None,
79
82
  no_print: Optional[bool] = None,
@@ -93,8 +96,8 @@ class DbtBaseOperator(BaseOperator):
93
96
  no_version_check: Optional[bool] = None,
94
97
  write_json: Optional[bool] = None,
95
98
  write_perf_info: Optional[bool] = None,
96
- anonymous_usage_stats: Optional[bool] = None,
97
- no_anonymous_usage_stats: Optional[bool] = None,
99
+ send_anonymous_usage_stats: Optional[bool] = None,
100
+ no_send_anonymous_usage_stats: Optional[bool] = None,
98
101
  # Extra features configuration
99
102
  dbt_conn_id: Optional[str] = "dbt_conn_id",
100
103
  profiles_conn_id: Optional[str] = None,
@@ -138,8 +141,8 @@ class DbtBaseOperator(BaseOperator):
138
141
  self.static_parser = static_parser
139
142
  self.no_static_parser = no_static_parser
140
143
 
141
- self.anonymous_usage_stats = anonymous_usage_stats
142
- self.no_anonymous_usage_stats = no_anonymous_usage_stats
144
+ self.send_anonymous_usage_stats = send_anonymous_usage_stats
145
+ self.no_send_anonymous_usage_stats = no_send_anonymous_usage_stats
143
146
 
144
147
  self.partial_parse = partial_parse
145
148
  self.no_partial_parse = no_partial_parse
@@ -671,13 +674,13 @@ def run_result_factory(data: list[tuple[Any, Any]]):
671
674
  We need to handle dt.datetime and agate.table.Table.
672
675
  The rest of the types should already be JSON-serializable.
673
676
  """
674
- from dbt.contracts.results import agate
677
+ from agate.table import Table
675
678
 
676
679
  d = {}
677
680
  for key, val in data:
678
681
  if isinstance(val, dt.datetime):
679
682
  val = val.isoformat()
680
- elif isinstance(val, agate.table.Table):
683
+ elif isinstance(val, Table):
681
684
  # agate Tables have a few print methods but they offer plain
682
685
  # text representations of the table which are not very JSON
683
686
  # friendly. There is a to_json method, but I don't think
@@ -1,7 +1,7 @@
1
1
  """A collection of configurations to interact with dbt-core."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
- import argparse
5
5
  import dataclasses
6
6
  import json
7
7
  import os
@@ -11,23 +11,24 @@ from typing import Any, Optional, Type, Union
11
11
 
12
12
  import dbt.flags as flags
13
13
  import yaml
14
- from dbt.adapters.factory import register_adapter
14
+ from dbt.cli.option_types import Package
15
15
  from dbt.clients import yaml_helper # type: ignore
16
16
  from dbt.config.profile import Profile, read_profile
17
17
  from dbt.config.project import PartialProject, Project
18
18
  from dbt.config.renderer import DbtProjectYamlRenderer, ProfileRenderer
19
19
  from dbt.config.runtime import RuntimeConfig
20
20
  from dbt.contracts.graph.manifest import Manifest
21
+ from dbt.exceptions import DbtProjectError
21
22
  from dbt.graph.graph import Graph
22
- from dbt.helper_types import WarnErrorOptions
23
+ from dbt.parser.manifest import parse_manifest
23
24
  from dbt.task.base import BaseTask, ConfiguredTask
24
25
  from dbt.task.build import BuildTask
25
26
  from dbt.task.clean import CleanTask
26
27
  from dbt.task.compile import CompileTask
27
28
  from dbt.task.debug import DebugTask
28
29
  from dbt.task.deps import DepsTask
30
+ from dbt.task.docs.generate import GenerateTask
29
31
  from dbt.task.freshness import FreshnessTask
30
- from dbt.task.generate import GenerateTask
31
32
  from dbt.task.list import ListTask
32
33
  from dbt.task.run import RunTask
33
34
  from dbt.task.run_operation import RunOperationTask
@@ -35,38 +36,10 @@ from dbt.task.runnable import GraphRunnableTask
35
36
  from dbt.task.seed import SeedTask
36
37
  from dbt.task.snapshot import SnapshotTask
37
38
  from dbt.task.test import TestTask
38
- from dbt.tracking import initialize_from_flags as orig_initialize_from_flags
39
+ from dbt.tracking import initialize_from_flags
39
40
 
40
41
  from airflow_dbt_python.utils.enums import FromStrEnum, LogFormat, Output
41
- from airflow_dbt_python.utils.version import (
42
- DBT_INSTALLED_GTE_1_5,
43
- DBT_INSTALLED_GTE_1_7,
44
- DBT_INSTALLED_LESS_THAN_1_5,
45
- )
46
-
47
- if DBT_INSTALLED_LESS_THAN_1_5:
48
- # dbt 1.5 removed the ParseTask as it only generated a manifest.
49
- # This task is now handled by a simple function.
50
- # Airflow-dbt-python should still support a ParseTask that does the same,
51
- # but this will require writing our own ParseTask class.
52
- # In the meantime, we treat ParseTask as CompileTask (which also generates
53
- # a manifest) if running with dbt-core >= 1.5.
54
- from dbt.task.parse import ParseTask # type: ignore
55
-
56
- # dbt 1.5 also changed the signature of initialize_from_flags, so we have
57
- # this for backwards compatibility.
58
- def initialize_from_flags(send_anonymous_usage_stats, profiles_dir):
59
- """Call initialize_from_flags without arguments.
60
-
61
- Added for backwards compatibility with versions of dbt < 1.5.
62
- """
63
- return orig_initialize_from_flags() # type: ignore
64
-
65
- else:
66
- from dbt.parser.manifest import ManifestLoader
67
-
68
- ParseTask = CompileTask
69
- initialize_from_flags = orig_initialize_from_flags
42
+ from airflow_dbt_python.utils.version import DBT_INSTALLED_GTE_1_9
70
43
 
71
44
 
72
45
  def parse_yaml_args(args: Optional[Union[str, dict[str, Any]]]) -> dict[str, Any]:
@@ -108,16 +81,16 @@ class BaseConfig:
108
81
  threads: Optional[int] = None
109
82
  use_experimental_parser: Optional[bool] = None
110
83
  store_failures: Optional[bool] = None
111
- vars: Optional[Union[str, dict[str, Any]]] = None
84
+ vars: Union[dict[str, Any]] = dataclasses.field(default_factory=dict)
112
85
  macro_debugging: Optional[bool] = None
113
86
  warn_error: Optional[bool] = None
114
87
  warn_error_options: dict[str, Any] = dataclasses.field(default_factory=dict)
115
88
  indirect_selection: str = "eager"
116
89
 
117
90
  # Logging
118
- log_format: Optional[LogFormat] = None
119
- log_format_file: Optional[LogFormat] = None
120
- log_path: Optional[str] = None if DBT_INSTALLED_LESS_THAN_1_5 else "logs"
91
+ log_format: LogFormat = LogFormat.DEFAULT
92
+ log_format_file: LogFormat = LogFormat.DEFAULT
93
+ log_path: Optional[str] = "logs"
121
94
  log_level: str = "info"
122
95
  log_level_file: str = "none"
123
96
  record_timing_info: Optional[str] = None
@@ -154,51 +127,38 @@ class BaseConfig:
154
127
  version_check: Optional[bool] = None
155
128
  no_version_check: Optional[bool] = dataclasses.field(default=None, repr=False)
156
129
 
157
- # dbt < 1.5
158
- anonymous_usage_stats: Optional[bool] = None
159
- no_anonymous_usage_stats: Optional[bool] = dataclasses.field(
130
+ send_anonymous_usage_stats: Optional[bool] = None
131
+ no_send_anonymous_usage_stats: Optional[bool] = dataclasses.field(
160
132
  default=None, repr=False
161
133
  )
162
134
 
163
- # dbt >= 1.5
164
- if DBT_INSTALLED_GTE_1_5:
165
- send_anonymous_usage_stats: Optional[bool] = None
166
- no_send_anonymous_usage_stats: Optional[bool] = dataclasses.field(
167
- default=None, repr=False
168
- )
169
-
170
- write_json: Optional[bool] = None
171
- no_write_json: Optional[bool] = dataclasses.field(default=None, repr=False)
135
+ include_saved_query: Optional[bool] = None
136
+ no_include_saved_query: Optional[bool] = dataclasses.field(default=None, repr=False)
172
137
 
173
- # dbt >= 1.7
174
- if DBT_INSTALLED_GTE_1_7:
175
- from dbt.cli.option_types import Package
138
+ clean_project_files_only: Optional[bool] = None
139
+ no_clean_project_files_only: Optional[bool] = dataclasses.field(
140
+ default=None, repr=False
141
+ )
176
142
 
177
- include_saved_query: Optional[bool] = None
178
- no_include_saved_query: Optional[bool] = dataclasses.field(
179
- default=None, repr=False
180
- )
143
+ require_resource_names_without_spaces: bool = False
181
144
 
182
- clean_project_files_only: Optional[bool] = None
183
- no_clean_project_files_only: Optional[bool] = dataclasses.field(
184
- default=None, repr=False
185
- )
145
+ add_package: Optional[Package] = None
146
+ dry_run: bool = False
147
+ lock: bool = False
148
+ static: bool = False
149
+ upgrade: bool = False
186
150
 
187
- add_package: Optional[Package] = None
188
- dry_run: bool = False
189
- lock: bool = False
190
- static: bool = False
191
- upgrade: bool = False
151
+ require_model_names_without_spaces: bool = False
152
+ source_freshness_run_project_hooks: bool = False
153
+ exclude_resource_types: list[str] = dataclasses.field(
154
+ default_factory=list, repr=False
155
+ )
192
156
 
193
157
  def __post_init__(self):
194
158
  """Post initialization actions for a dbt configuration."""
195
- self.parsed_vars = parse_yaml_args(self.vars)
196
- self.vars = yaml.dump(self.parsed_vars)
159
+ self.vars = parse_yaml_args(self.vars)
197
160
  self.set_mutually_exclusive_attributes()
198
161
 
199
- self.send_anonymous_usage_stats = self.anonymous_usage_stats
200
- self.no_send_anonymous_usage_stats = self.no_anonymous_usage_stats
201
-
202
162
  def set_mutually_exclusive_attributes(self):
203
163
  """Support pairs of mutually exclusive parameters.
204
164
 
@@ -219,7 +179,6 @@ class BaseConfig:
219
179
  ("populate_cache", True),
220
180
  ("static_parser", True),
221
181
  ("version_check", True),
222
- ("anonymous_usage_stats", True),
223
182
  ("send_anonymous_usage_stats", True),
224
183
  ("write_json", True),
225
184
  ("include_saved_query", None),
@@ -252,40 +211,6 @@ class BaseConfig:
252
211
  item = item.lower()
253
212
  return super().__getattribute__(item)
254
213
 
255
- def to_flags(self) -> argparse.Namespace:
256
- """Return a Namespace to use as dbt flags."""
257
- flags_dict = vars(flags.get_flags())
258
- flags_dict["MACRO_DEBUGGING"] = self.macro_debugging
259
- flags_dict["WARN_ERROR"] = self.warn_error
260
- flags_dict["WARN_ERROR_OPTIONS"] = WarnErrorOptions(
261
- include=self.warn_error_options.get("include", []),
262
- exclude=self.warn_error_options.get("exclude", []),
263
- )
264
-
265
- flags_dict["PRINTER_WIDTH"] = self.printer_width
266
- flags_dict["TARGET_PATH"] = None
267
- flags_dict["LOG_PATH"] = self.log_path
268
- flags_dict["LOG_FORMAT"] = self.log_format
269
- flags_dict["LOG_FORMAT_FILE"] = self.log_format
270
- flags_dict["SEND_ANONYMOUS_USAGE_STATS"] = self.send_anonymous_usage_stats
271
- flags_dict["PARTIAL_PARSE"] = self.partial_parse
272
- flags_dict["STATIC_PARSER"] = self.static_parser
273
- flags_dict["PROFILES_DIR"] = self.profiles_dir
274
- flags_dict["STORE_FAILURES"] = self.store_failures
275
- flags_dict["LOG_LEVEL"] = self.log_level
276
- flags_dict["LOG_LEVEL_FILE"] = self.log_level_file
277
- flags_dict["LOG_CACHE_EVENTS"] = self.log_cache_events
278
- flags_dict["QUIET"] = self.quiet
279
- flags_dict["DEBUG"] = self.debug
280
- flags_dict["USE_COLORS_FILE"] = self.use_colors_file
281
- flags_dict["WRITE_JSON"] = self.write_json
282
- flags_dict["INDIRECT_SELECTION"] = getattr(self, "indirect_selection", None)
283
- flags_dict["FAIL_FAST"] = self.fail_fast
284
- flags_dict["INTROSPECT"] = self.introspect
285
-
286
- flags_ns = argparse.Namespace(**flags_dict)
287
- return flags_ns
288
-
289
214
  @property
290
215
  def dbt_task(self) -> Type[BaseTask]:
291
216
  """Access to the underlying dbt task class."""
@@ -376,30 +301,36 @@ class BaseConfig:
376
301
  A tuple with the corresponding subclass of a dbt BaseTask and the
377
302
  RuntimeConfig for the task.
378
303
  """
379
- if not DBT_INSTALLED_LESS_THAN_1_5:
380
- local_flags = self.to_flags()
381
- flags.set_flags(local_flags)
382
-
304
+ flags.set_from_args(self, {}) # type: ignore
383
305
  project, profile = self.create_dbt_project_and_profile(extra_targets)
306
+
307
+ if not DBT_INSTALLED_GTE_1_9:
308
+ self.cls.set_log_format() # type: ignore[attr-defined]
309
+
310
+ initialize_from_flags(self.send_anonymous_usage_stats, self.profiles_dir)
311
+
312
+ local_flags = flags.get_flags()
313
+
384
314
  runtime_config = self.create_runtime_config(project, profile)
385
315
 
386
- if not DBT_INSTALLED_LESS_THAN_1_5:
387
- if issubclass(self.dbt_task, ConfiguredTask) and runtime_config:
388
- register_adapter(runtime_config)
389
- manifest = ManifestLoader.get_full_manifest(
390
- runtime_config, write_perf_info=write_perf_info
391
- )
392
- task: BaseTask = self.dbt_task(
393
- args=self, config=runtime_config, manifest=manifest
394
- )
395
- elif issubclass(self.dbt_task, DepsTask):
396
- task = self.dbt_task(args=self, project=project)
397
- elif issubclass(self.dbt_task, DebugTask):
398
- task = self.dbt_task(args=self, config=runtime_config)
399
- else:
400
- task = self.dbt_task(args=self, config=runtime_config, project=project)
316
+ if issubclass(self.dbt_task, ConfiguredTask) and runtime_config:
317
+ manifest = parse_manifest(
318
+ runtime_config,
319
+ write_perf_info=write_perf_info,
320
+ write=False,
321
+ write_json=False,
322
+ )
323
+ task: BaseTask = self.dbt_task(
324
+ args=local_flags, config=runtime_config, manifest=manifest
325
+ )
326
+ elif issubclass(self.dbt_task, DepsTask):
327
+ task = self.dbt_task(args=local_flags, project=project)
328
+ elif issubclass(self.dbt_task, DebugTask):
329
+ task = self.dbt_task(args=local_flags)
330
+ elif issubclass(self.dbt_task, CleanTask):
331
+ task = self.dbt_task(args=local_flags, config=project)
401
332
  else:
402
- task = self.dbt_task(args=self, config=runtime_config)
333
+ task = self.dbt_task(args=local_flags)
403
334
 
404
335
  if self.compiled_target is not None:
405
336
  # Only supported by subclasses of dbt's GraphRunnableTask.
@@ -415,7 +346,6 @@ class BaseConfig:
415
346
 
416
347
  If the given task's ConfigType is not RuntimeConfig, None will be returned
417
348
  instead.
418
- Flags are also initialized as configuration is created.
419
349
 
420
350
  Args:
421
351
  project: dbt project.
@@ -424,17 +354,13 @@ class BaseConfig:
424
354
  Returns:
425
355
  A RuntimeConfig instance.
426
356
  """
427
- initialize_from_flags(self.send_anonymous_usage_stats, self.profiles_dir)
428
- self.cls.set_log_format()
429
- flags.set_from_args(self, profile.user_config) # type: ignore
430
-
431
- cfg_type = self.dbt_task.ConfigType
357
+ if isinstance(self.vars, str):
358
+ self.vars = yaml.safe_load(self.vars)
432
359
 
433
- if issubclass(cfg_type, RuntimeConfig):
434
- if not DBT_INSTALLED_LESS_THAN_1_5 and isinstance(self.vars, str):
435
- self.vars = yaml.safe_load(self.vars)
436
- return cfg_type.from_parts(project=project, profile=profile, args=self)
437
- return None
360
+ try:
361
+ return RuntimeConfig.from_parts(project=project, profile=profile, args=self)
362
+ except DbtProjectError:
363
+ return None
438
364
 
439
365
  def create_dbt_project_and_profile(
440
366
  self, extra_targets: Optional[dict[str, Any]] = None
@@ -449,7 +375,7 @@ class BaseConfig:
449
375
  """
450
376
  profile = self.create_dbt_profile(extra_targets)
451
377
 
452
- project_renderer = DbtProjectYamlRenderer(profile, self.parsed_vars)
378
+ project_renderer = DbtProjectYamlRenderer(profile, self.vars)
453
379
  project = Project.from_project_root(
454
380
  self.project_dir or ".",
455
381
  project_renderer,
@@ -480,7 +406,7 @@ class BaseConfig:
480
406
  @property
481
407
  def profile_renderer(self) -> ProfileRenderer:
482
408
  """Return a ProfileRenderer with this config's parsed vars."""
483
- profile_renderer = ProfileRenderer(self.parsed_vars)
409
+ profile_renderer = ProfileRenderer(self.vars)
484
410
  return profile_renderer
485
411
 
486
412
  @property
@@ -513,6 +439,11 @@ class BaseConfig:
513
439
  Returns:
514
440
  A dbt profile for the task represented by this configuration.
515
441
  """
442
+ from dbt_common.clients.system import get_env
443
+ from dbt_common.context import set_invocation_context
444
+
445
+ set_invocation_context(get_env())
446
+
516
447
  if self.profiles_dir is not None:
517
448
  raw_profiles = read_profile(self.profiles_dir)
518
449
  else:
@@ -524,15 +455,12 @@ class BaseConfig:
524
455
  outputs.setdefault("target", self.target)
525
456
  profile["outputs"] = {**outputs, **extra_targets}
526
457
 
527
- user_config = raw_profiles.get("config", {})
528
-
529
458
  profile = Profile.from_raw_profile_info(
530
459
  raw_profile=raw_profiles.get(
531
460
  self.profile_name, {}
532
461
  ), # Let dbt handle missing profile errors.
533
462
  profile_name=self.profile_name,
534
463
  renderer=self.profile_renderer,
535
- user_config=user_config,
536
464
  target_override=self.target,
537
465
  threads_override=self.threads,
538
466
  )
@@ -668,7 +596,7 @@ class ParseTaskConfig(SelectionConfig):
668
596
  TODO: Temporarily, handle this as a CompileTask.
669
597
  """
670
598
 
671
- cls: Type[BaseTask] = dataclasses.field(default=ParseTask, init=False)
599
+ cls: Type[BaseTask] = dataclasses.field(default=CompileTask, init=False)
672
600
  compile: Optional[bool] = None
673
601
  which: str = dataclasses.field(default="parse", init=False)
674
602
  write_manifest: Optional[bool] = None
@@ -694,8 +622,6 @@ class RunOperationTaskConfig(BaseConfig):
694
622
  def __post_init__(self):
695
623
  """Support dictionary args by casting them to str after setting."""
696
624
  super().__post_init__()
697
- if DBT_INSTALLED_LESS_THAN_1_5:
698
- self.args = str(self.args)
699
625
 
700
626
 
701
627
  @dataclasses.dataclass
@@ -734,6 +660,7 @@ class TestTaskConfig(SelectionConfig):
734
660
  singular: Optional[bool] = None
735
661
  store_failures: Optional[bool] = None
736
662
  which: str = dataclasses.field(default="test", init=False)
663
+ resource_types: Optional[list[str]] = None
737
664
 
738
665
  def __post_init__(self):
739
666
  """Support for type casting arguments."""
@@ -1,4 +1,5 @@
1
1
  """Enumeration subclasses used in dbt configurations."""
2
+
2
3
  from enum import Enum
3
4
  from typing import Type, TypeVar
4
5
 
@@ -24,6 +25,7 @@ class LogFormat(str, Enum):
24
25
  """Allowed dbt log formats."""
25
26
 
26
27
  DEFAULT = "default"
28
+ DEBUG = "debug"
27
29
  JSON = "json"
28
30
  TEXT = "text"
29
31
 
@@ -1,4 +1,5 @@
1
1
  """Provides utilities to interact with environment variables."""
2
+
2
3
  import copy
3
4
  import os
4
5
  from contextlib import contextmanager
@@ -1,4 +1,5 @@
1
1
  """A URL class to locate dbt resources."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import os
@@ -464,7 +465,7 @@ def tar_dir_from_url(url: URL, tar_url: URL) -> None:
464
465
  except IndexError:
465
466
  compression = ""
466
467
 
467
- with tarfile.open(tar_url, f"w:{compression}") as tf:
468
+ with tarfile.open(tar_url, f"w:{compression}") as tf: # type: ignore[call-overload]
468
469
  for _file in url:
469
470
  tf.add(_file)
470
471
 
@@ -472,6 +473,6 @@ def tar_dir_from_url(url: URL, tar_url: URL) -> None:
472
473
  if __name__ == "__main__":
473
474
  import doctest
474
475
 
475
- import airflow_dbt_python.utils.url as url
476
+ import airflow_dbt_python.utils.url as url_utils
476
477
 
477
- doctest.testmod(url)
478
+ doctest.testmod(url_utils)
@@ -3,28 +3,24 @@
3
3
  These are only used to ensure backwards compatibility with older versions of dbt.
4
4
  """
5
5
 
6
- from dbt.semver import Matchers, VersionSpecifier
6
+ try:
7
+ from dbt.semver import Matchers, VersionSpecifier
8
+ except ImportError:
9
+ from dbt_common.semver import Matchers, VersionSpecifier
10
+
7
11
  from dbt.version import installed
8
12
 
9
- DBT_1_5 = VersionSpecifier(
10
- major="1", minor="5", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
11
- )
12
- DBT_1_6 = VersionSpecifier(
13
- major="1", minor="6", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
14
- )
15
- DBT_1_7 = VersionSpecifier(
16
- major="1", minor="7", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
17
- )
18
13
  DBT_1_8 = VersionSpecifier(
19
14
  major="1", minor="8", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
20
15
  )
16
+ DBT_1_9 = VersionSpecifier(
17
+ major="1", minor="9", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
18
+ )
19
+ DBT_2_0 = VersionSpecifier(
20
+ major="2", minor="0", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
21
+ )
21
22
 
22
- DBT_INSTALLED_LESS_THAN_1_5 = installed < DBT_1_5
23
-
24
- DBT_INSTALLED_GTE_1_5 = installed.compare(DBT_1_5) == 1
25
- DBT_INSTALLED_GTE_1_6 = installed.compare(DBT_1_6) == 1
26
- DBT_INSTALLED_GTE_1_7 = installed.compare(DBT_1_7) == 1
23
+ DBT_INSTALLED_GTE_1_9 = installed.compare(DBT_1_9) == 1
27
24
 
28
- DBT_INSTALLED_1_5 = DBT_1_5 < installed < DBT_1_6
29
- DBT_INSTALLED_1_6 = DBT_1_6 < installed < DBT_1_7
30
- DBT_INSTALLED_1_7 = DBT_1_7 < installed < DBT_1_8
25
+ DBT_INSTALLED_1_8 = DBT_1_8 < installed < DBT_1_9
26
+ DBT_INSTALLED_1_9 = DBT_1_9 < installed < DBT_2_0
@@ -1,24 +1,19 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: airflow-dbt-python
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: A collection of Airflow operators, hooks, and utilities to execute dbt commands
5
- Home-page: https://github.com/tomasfarias/airflow-dbt-python
6
5
  License: MIT
7
6
  Author: Tomás Farías Santana
8
7
  Author-email: tomas@tomasfarias.dev
9
- Requires-Python: >=3.8,<3.12
8
+ Requires-Python: >=3.9,<3.13
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
13
  Classifier: Programming Language :: Python :: 3.9
16
14
  Classifier: Programming Language :: Python :: 3.10
17
15
  Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.8
21
- Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.12
22
17
  Provides-Extra: adapters
23
18
  Provides-Extra: airflow-providers
24
19
  Provides-Extra: bigquery
@@ -29,23 +24,17 @@ Provides-Extra: redshift
29
24
  Provides-Extra: s3
30
25
  Provides-Extra: snowflake
31
26
  Provides-Extra: spark
32
- Requires-Dist: apache-airflow (>=2.2)
27
+ Requires-Dist: apache-airflow (>=2.8)
33
28
  Requires-Dist: apache-airflow-providers-amazon (>=3.0.0) ; extra == "airflow-providers" or extra == "s3"
34
29
  Requires-Dist: apache-airflow-providers-ssh (>=3.0.0) ; extra == "airflow-providers" or extra == "git"
35
30
  Requires-Dist: connexion (<3.0)
36
31
  Requires-Dist: contextlib-chdir (==1.0.2) ; python_version < "3.11"
37
- Requires-Dist: dbt-bigquery (>=1.4.0,<1.6.0) ; (python_version < "3.8") and (extra == "adapters" or extra == "bigquery")
38
- Requires-Dist: dbt-bigquery (>=1.4.0,<1.8.0) ; (python_version >= "3.11") and (extra == "adapters" or extra == "bigquery")
39
- Requires-Dist: dbt-core (>=1.4.0,<1.6.0) ; python_version < "3.8"
40
- Requires-Dist: dbt-core (>=1.4.0,<1.8.0) ; python_version >= "3.11"
41
- Requires-Dist: dbt-postgres (>=1.4.0,<1.6.0) ; (python_version < "3.8") and (extra == "adapters" or extra == "postgres")
42
- Requires-Dist: dbt-postgres (>=1.4.0,<1.8.0) ; (python_version >= "3.11") and (extra == "adapters" or extra == "postgres")
43
- Requires-Dist: dbt-redshift (>=1.4.0,<1.6.0) ; (python_version < "3.8") and (extra == "adapters" or extra == "redshift")
44
- Requires-Dist: dbt-redshift (>=1.4.0,<1.8.0) ; (python_version >= "3.11") and (extra == "adapters" or extra == "redshift")
45
- Requires-Dist: dbt-snowflake (>=1.4.0,<1.6.0) ; (python_version < "3.8") and (extra == "adapters" or extra == "snowflake")
46
- Requires-Dist: dbt-snowflake (>=1.4.0,<1.8.0) ; (python_version >= "3.11") and (extra == "adapters" or extra == "snowflake")
47
- Requires-Dist: dbt-spark (>=1.4.0,<1.6.0) ; (python_version < "3.8") and (extra == "adapters" or extra == "spark")
48
- Requires-Dist: dbt-spark (>=1.4.0,<1.8.0) ; (python_version >= "3.11") and (extra == "adapters" or extra == "spark")
32
+ Requires-Dist: dbt-bigquery (>=1.8.0,<2.0.0) ; extra == "adapters" or extra == "bigquery"
33
+ Requires-Dist: dbt-core (>=1.8.0,<2.0.0)
34
+ Requires-Dist: dbt-postgres (>=1.8.0,<2.0.0) ; extra == "adapters" or extra == "postgres"
35
+ Requires-Dist: dbt-redshift (>=1.8.0,<2.0.0) ; extra == "adapters" or extra == "redshift"
36
+ Requires-Dist: dbt-snowflake (>=1.8.0,<2.0.0) ; extra == "adapters" or extra == "snowflake"
37
+ Requires-Dist: dbt-spark (>=1.8.0,<2.0.0) ; extra == "adapters" or extra == "spark"
49
38
  Requires-Dist: dulwich (>=0.21) ; extra == "git"
50
39
  Project-URL: Repository, https://github.com/tomasfarias/airflow-dbt-python
51
40
  Description-Content-Type: text/markdown
@@ -67,10 +56,10 @@ Read the [documentation](https://airflow-dbt-python.readthedocs.io) for examples
67
56
  ## Requirements
68
57
 
69
58
  Before using *airflow-dbt-python*, ensure you meet the following requirements:
70
- * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.4.0 or later.
71
- * An Airflow environment using version 2.2 or later.
59
+ * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.8 or later.
60
+ * An Airflow environment using version 2.8 or later.
72
61
 
73
- * If using any managed service, like AWS MWAA, ensure your environment is created with a supported version of Airflow.
62
+ * If using any managed service, like AWS MWAA or GCP Cloud Composer 2/3, ensure your environment is created with a supported version of Airflow.
74
63
  * If self-hosting, Airflow installation instructions can be found in their [official documentation](https://airflow.apache.org/docs/apache-airflow/stable/installation/index.html).
75
64
 
76
65
  * Running Python 3.8 or later in your Airflow environment.
@@ -81,7 +70,7 @@ Before using *airflow-dbt-python*, ensure you meet the following requirements:
81
70
 
82
71
  > **Note**
83
72
  >
84
- > Older versions of Airflow and *dbt* may work with *airflow-dbt-python*, although we cannot guarantee this. Our testing pipeline runs the latest *dbt-core* with the latest Airflow release, and the latest version supported by [AWS MWAA](https://aws.amazon.com/managed-workflows-for-apache-airflow/).
73
+ > Older versions of Airflow and *dbt* may work with *airflow-dbt-python*, although we cannot guarantee this. Our testing pipeline runs the latest *dbt-core* with the latest Airflow release, and the latest version supported by [AWS MWAA](https://aws.amazon.com/managed-workflows-for-apache-airflow/) and [GCP Cloud Composer 2/3](https://aws.amazon.com/managed-workflows-for-apache-airflow/).
85
74
 
86
75
  ## From PyPI
87
76
 
@@ -118,6 +107,12 @@ Add *airflow-dbt-python* to your `requirements.txt` file and edit your Airflow e
118
107
 
119
108
  Read the [documentation](https://airflow-dbt-python.readthedocs.io/en/latest/getting_started.html#installing-in-mwaa) for more a more detailed AWS MWAA installation breakdown.
120
109
 
110
+ ## In GCP Cloud Composer
111
+
112
+ Add *airflow-dbt-python* to your PyPI packages list.
113
+
114
+ Read the [documentation](https://cloud.google.com/composer/docs/composer-2/install-python-dependencies#install-pypi) for more a more detailed GCP Cloud Composer 2 installation breakdown.
115
+
121
116
  ## In other managed services
122
117
 
123
118
  *airflow-dbt-python* should be compatible with most or all Airflow managed services. Consult the documentation specific to your provider.
@@ -171,7 +166,7 @@ See an example DAG [here](examples/airflow_connection_target_dag.py).
171
166
 
172
167
  Although [`dbt`](https://docs.getdbt.com/) is meant to be installed and used as a CLI, we may not have control of the environment where Airflow is running, disallowing us the option of using *dbt* as a CLI.
173
168
 
174
- This is exactly what happens when using [Amazon's Managed Workflows for Apache Airflow](https://aws.amazon.com/managed-workflows-for-apache-airflow/) or MWAA: although a list of Python requirements can be passed, the CLI cannot be found in the worker's PATH.
169
+ This is exactly what happens when using [Amazon's Managed Workflows for Apache Airflow](https://aws.amazon.com/managed-workflows-for-apache-airflow/) (aka MWAA): although a list of Python requirements can be passed, the CLI cannot be found in the worker's PATH.
175
170
 
176
171
  There is a workaround which involves using Airflow's `BashOperator` and running Python from the command line:
177
172
 
@@ -0,0 +1,20 @@
1
+ airflow_dbt_python/__init__.py,sha256=qYIsTHy4p3NX0QYIsLdatz6GlnaMwYsSoD3wtPD0BqY,153
2
+ airflow_dbt_python/__version__.py,sha256=R3gvi55VePWbBsovUbmgfU1cEWnzuFVlZFXx74rtufo,190
3
+ airflow_dbt_python/hooks/__init__.py,sha256=dcQQ5e8EpcQ_CzKVOeg_cRbqOEmloNLnWSp3B0bDSXs,57
4
+ airflow_dbt_python/hooks/dbt.py,sha256=_M-WnwAgUtlpWP2hIa4g9ZlCy5QFXSYQOL_S5ARucYk,20001
5
+ airflow_dbt_python/hooks/git.py,sha256=bxzyd1g9WPfZs8ukj-FSafrVki4hyw_GF06jF61ncxk,7126
6
+ airflow_dbt_python/hooks/localfs.py,sha256=i49VldNRFyvsd8bMdpkWaaviP2VoqMnl-VmHYgrpfZM,5387
7
+ airflow_dbt_python/hooks/remote.py,sha256=Wx6zP9DH6blIiremwicxgYFxssQ-5Na9UaHyZjcNJSI,5478
8
+ airflow_dbt_python/hooks/s3.py,sha256=AUYtpI609fBzZXCY7esJbdFhijHoMVJWa6lERPFuAro,7205
9
+ airflow_dbt_python/operators/__init__.py,sha256=zveHW17YWoCpdsQhCZtvdPoEiFiwWykqPnAnOAQ8vk0,46
10
+ airflow_dbt_python/operators/dbt.py,sha256=6WF6yxjNJZI5HgVLA5eEnoBK7V8Jz2MZcfgEmAXRLg8,22098
11
+ airflow_dbt_python/utils/__init__.py,sha256=J3_g-tV35XkrZSwYULsAq4B0TUa6rw0uxP-LFwl_k2Q,45
12
+ airflow_dbt_python/utils/configs.py,sha256=BfYyws28MnMYvqgN9YcnrYWXa_x1cs9fmUknbmGFijI,25322
13
+ airflow_dbt_python/utils/enums.py,sha256=rktUM8cTkEUsdNbzd_f6fxfgNiZGifJfOr0zvrsMyuQ,969
14
+ airflow_dbt_python/utils/env.py,sha256=o2Y3RMyXADSpUCU-G0yRiqXB-yo3oPsXtCEI7H5GIyM,646
15
+ airflow_dbt_python/utils/url.py,sha256=Ac7PJMl3AsrVMXGPgFdzOrAVsvCfS4hMWAtwgRMdbP0,16111
16
+ airflow_dbt_python/utils/version.py,sha256=RAMagzZofIAiFUFc9g06yZXM81BaMI3hH58q4xjfE0M,811
17
+ airflow_dbt_python-2.2.0.dist-info/LICENSE,sha256=8yx5JCe-4cW52JiqVrMlA990r7tuac2foiUryhV9dfY,1073
18
+ airflow_dbt_python-2.2.0.dist-info/METADATA,sha256=Lfx7ptaVOY1ABFbIZe04Ru-ODdZMQf3ie8ct_Hx1LvA,15544
19
+ airflow_dbt_python-2.2.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
20
+ airflow_dbt_python-2.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.4.0
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,20 +0,0 @@
1
- airflow_dbt_python/__init__.py,sha256=l_rEXiRH5IyzQ5xExdPwcR-HMqBJFqlnX0GSZpDpFJM,152
2
- airflow_dbt_python/__version__.py,sha256=afYljsyh04NUgREwaXVXz71v3JWcvWkGVewgoy4JW-0,189
3
- airflow_dbt_python/hooks/__init__.py,sha256=dcQQ5e8EpcQ_CzKVOeg_cRbqOEmloNLnWSp3B0bDSXs,57
4
- airflow_dbt_python/hooks/dbt.py,sha256=fu6T1CTN1Sur69BZbPBvvkxx3qsCJkm3vZD9C8LsAVk,21162
5
- airflow_dbt_python/hooks/git.py,sha256=u35yQM8FEfv0zfP37bPkN8rO2kp_GdCzQqBg9-pR0iY,7113
6
- airflow_dbt_python/hooks/localfs.py,sha256=TKVh6kuFXiFo-GCNNfZwwtQ8Te99l1hpOnEYStohtWc,5386
7
- airflow_dbt_python/hooks/remote.py,sha256=nDGeGXUl7vNs2uhulcrtxHXabWYzuzJVwOTp94-XsZ0,5477
8
- airflow_dbt_python/hooks/s3.py,sha256=LwLOOORg8-Ts3pert0MF6G8VFLkABsB_3m3AZNcVGrI,7204
9
- airflow_dbt_python/operators/__init__.py,sha256=zveHW17YWoCpdsQhCZtvdPoEiFiwWykqPnAnOAQ8vk0,46
10
- airflow_dbt_python/operators/dbt.py,sha256=c9U-lMwM44NeUZUzpdRXyN2pNWlx0A0DnjJBupXQ1yk,22043
11
- airflow_dbt_python/utils/__init__.py,sha256=J3_g-tV35XkrZSwYULsAq4B0TUa6rw0uxP-LFwl_k2Q,45
12
- airflow_dbt_python/utils/configs.py,sha256=leb7-feu3bGFDi4vS0eurgoAKoa_xOUVMTDxo-yuyEk,28805
13
- airflow_dbt_python/utils/enums.py,sha256=1pWwJe5MUI9eebbDufL44ILj_7sJ7hJA0v2MBCrnPok,948
14
- airflow_dbt_python/utils/env.py,sha256=4hY12-C44D5cq-6Vqxf68Cv9KERVRIloLgtSeN5WHw0,645
15
- airflow_dbt_python/utils/url.py,sha256=euuCJey7QoaBJATgB63uxGR5yGBbw5_eMnoqPRKvudA,16067
16
- airflow_dbt_python/utils/version.py,sha256=BO5jCI-aQb9UdZ6txUniawNu-ZUnj4F2DUO8oXBuu0M,1039
17
- airflow_dbt_python-2.0.1.dist-info/METADATA,sha256=fvAiknzyzp5rH3TAhLqOqtSnEuS0qxykyfT8vBHTSLU,16279
18
- airflow_dbt_python-2.0.1.dist-info/LICENSE,sha256=8yx5JCe-4cW52JiqVrMlA990r7tuac2foiUryhV9dfY,1073
19
- airflow_dbt_python-2.0.1.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
20
- airflow_dbt_python-2.0.1.dist-info/RECORD,,