airflow-dbt-python 2.0.1__tar.gz → 2.1.0__tar.gz

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 (22) hide show
  1. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/PKG-INFO +21 -20
  2. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/README.md +11 -5
  3. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/__init__.py +1 -0
  4. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/__version__.py +2 -1
  5. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/dbt.py +26 -47
  6. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/git.py +2 -1
  7. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/localfs.py +1 -0
  8. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/remote.py +1 -0
  9. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/s3.py +1 -0
  10. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/operators/dbt.py +11 -8
  11. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/configs.py +77 -116
  12. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/enums.py +2 -0
  13. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/env.py +1 -0
  14. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/url.py +1 -0
  15. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/version.py +10 -13
  16. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/pyproject.toml +28 -56
  17. airflow_dbt_python-2.1.0/setup.py +51 -0
  18. airflow_dbt_python-2.0.1/setup.py +0 -81
  19. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/LICENSE +0 -0
  20. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/__init__.py +0 -0
  21. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/operators/__init__.py +0 -0
  22. {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/__init__.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airflow-dbt-python
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: A collection of Airflow operators, hooks, and utilities to execute dbt commands
5
5
  Home-page: https://github.com/tomasfarias/airflow-dbt-python
6
6
  License: MIT
7
7
  Author: Tomás Farías Santana
8
8
  Author-email: tomas@tomasfarias.dev
9
- Requires-Python: >=3.8,<3.12
9
+ Requires-Python: >=3.8,<3.13
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
20
21
  Classifier: Programming Language :: Python :: 3.8
21
22
  Classifier: Programming Language :: Python :: 3.9
22
23
  Provides-Extra: adapters
@@ -29,23 +30,17 @@ Provides-Extra: redshift
29
30
  Provides-Extra: s3
30
31
  Provides-Extra: snowflake
31
32
  Provides-Extra: spark
32
- Requires-Dist: apache-airflow (>=2.2)
33
+ Requires-Dist: apache-airflow (>=2.7)
33
34
  Requires-Dist: apache-airflow-providers-amazon (>=3.0.0) ; extra == "airflow-providers" or extra == "s3"
34
35
  Requires-Dist: apache-airflow-providers-ssh (>=3.0.0) ; extra == "airflow-providers" or extra == "git"
35
36
  Requires-Dist: connexion (<3.0)
36
37
  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")
38
+ Requires-Dist: dbt-bigquery (>=1.7.0,<1.9.0) ; extra == "adapters" or extra == "bigquery"
39
+ Requires-Dist: dbt-core (>=1.7.5,<1.9.0)
40
+ Requires-Dist: dbt-postgres (>=1.7.0,<1.9.0) ; extra == "adapters" or extra == "postgres"
41
+ Requires-Dist: dbt-redshift (>=1.7.0,<1.9.0) ; extra == "adapters" or extra == "redshift"
42
+ Requires-Dist: dbt-snowflake (>=1.7.0,<1.9.0) ; extra == "adapters" or extra == "snowflake"
43
+ Requires-Dist: dbt-spark (>=1.7.0,<1.9.0) ; extra == "adapters" or extra == "spark"
49
44
  Requires-Dist: dulwich (>=0.21) ; extra == "git"
50
45
  Project-URL: Repository, https://github.com/tomasfarias/airflow-dbt-python
51
46
  Description-Content-Type: text/markdown
@@ -67,10 +62,10 @@ Read the [documentation](https://airflow-dbt-python.readthedocs.io) for examples
67
62
  ## Requirements
68
63
 
69
64
  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.
65
+ * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.7.5 or later.
66
+ * An Airflow environment using version 2.7 or later.
72
67
 
73
- * If using any managed service, like AWS MWAA, ensure your environment is created with a supported version of Airflow.
68
+ * 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
69
  * 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
70
 
76
71
  * Running Python 3.8 or later in your Airflow environment.
@@ -81,7 +76,7 @@ Before using *airflow-dbt-python*, ensure you meet the following requirements:
81
76
 
82
77
  > **Note**
83
78
  >
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/).
79
+ > 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
80
 
86
81
  ## From PyPI
87
82
 
@@ -118,6 +113,12 @@ Add *airflow-dbt-python* to your `requirements.txt` file and edit your Airflow e
118
113
 
119
114
  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
115
 
116
+ ## In GCP Cloud Composer
117
+
118
+ Add *airflow-dbt-python* to your PyPI packages list.
119
+
120
+ 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.
121
+
121
122
  ## In other managed services
122
123
 
123
124
  *airflow-dbt-python* should be compatible with most or all Airflow managed services. Consult the documentation specific to your provider.
@@ -171,7 +172,7 @@ See an example DAG [here](examples/airflow_connection_target_dag.py).
171
172
 
172
173
  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
174
 
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.
175
+ 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
176
 
176
177
  There is a workaround which involves using Airflow's `BashOperator` and running Python from the command line:
177
178
 
@@ -15,10 +15,10 @@ Read the [documentation](https://airflow-dbt-python.readthedocs.io) for examples
15
15
  ## Requirements
16
16
 
17
17
  Before using *airflow-dbt-python*, ensure you meet the following requirements:
18
- * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.4.0 or later.
19
- * An Airflow environment using version 2.2 or later.
18
+ * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.7.5 or later.
19
+ * An Airflow environment using version 2.7 or later.
20
20
 
21
- * If using any managed service, like AWS MWAA, ensure your environment is created with a supported version of Airflow.
21
+ * 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.
22
22
  * If self-hosting, Airflow installation instructions can be found in their [official documentation](https://airflow.apache.org/docs/apache-airflow/stable/installation/index.html).
23
23
 
24
24
  * Running Python 3.8 or later in your Airflow environment.
@@ -29,7 +29,7 @@ Before using *airflow-dbt-python*, ensure you meet the following requirements:
29
29
 
30
30
  > **Note**
31
31
  >
32
- > 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/).
32
+ > 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/).
33
33
 
34
34
  ## From PyPI
35
35
 
@@ -66,6 +66,12 @@ Add *airflow-dbt-python* to your `requirements.txt` file and edit your Airflow e
66
66
 
67
67
  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.
68
68
 
69
+ ## In GCP Cloud Composer
70
+
71
+ Add *airflow-dbt-python* to your PyPI packages list.
72
+
73
+ 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.
74
+
69
75
  ## In other managed services
70
76
 
71
77
  *airflow-dbt-python* should be compatible with most or all Airflow managed services. Consult the documentation specific to your provider.
@@ -119,7 +125,7 @@ See an example DAG [here](examples/airflow_connection_target_dag.py).
119
125
 
120
126
  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.
121
127
 
122
- 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.
128
+ 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.
123
129
 
124
130
  There is a workaround which involves using Airflow's `BashOperator` and running Python from the command line:
125
131
 
@@ -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.1.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,13 @@ 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
+ from airflow_dbt_python.utils.version import DBT_INSTALLED_GTE_1_8
28
29
 
29
30
  if sys.version_info >= (3, 11):
30
31
  from contextlib import chdir as chdir_ctx
31
32
  else:
32
33
  from contextlib_chdir import chdir as chdir_ctx
33
34
 
34
-
35
35
  if TYPE_CHECKING:
36
36
  from dbt.contracts.results import RunResult
37
37
  from dbt.task.base import BaseTask
@@ -227,18 +227,11 @@ class DbtHook(BaseHook):
227
227
  A tuple containing a boolean indicating success and optionally the results
228
228
  of running the dbt command.
229
229
  """
230
- from dbt.adapters.factory import register_adapter
230
+ from dbt.adapters.factory import adapter_management
231
231
  from dbt.task.base import get_nearest_project_dir
232
232
  from dbt.task.clean import CleanTask
233
233
  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
234
+ from dbt.tracking import track_run
242
235
 
243
236
  config = self.get_dbt_task_config(command, **kwargs)
244
237
  extra_target = self.get_dbt_target_from_connection(config.target)
@@ -252,36 +245,24 @@ class DbtHook(BaseHook):
252
245
  ) as dbt_dir:
253
246
  # When creating tasks via from_args, dbt switches to the project directory.
254
247
  # 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)
248
+ nearest_project_dir = get_nearest_project_dir(config.project_dir)
262
249
 
263
250
  with chdir_ctx(nearest_project_dir):
264
- config.dbt_task.pre_init_hook(config)
265
251
  self.ensure_profiles(config)
266
252
 
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)
253
+ with adapter_management():
254
+ task, runtime_config = config.create_dbt_task(
255
+ extra_target, write_perf_info
256
+ )
257
+ requires_profile = isinstance(task, (CleanTask, DepsTask))
273
258
 
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()
259
+ self.setup_dbt_logging(task, config.debug)
279
260
 
280
- results = None
281
- with adapter_management():
282
- if not requires_profile:
283
- if runtime_config is not None:
284
- register_adapter(runtime_config)
261
+ if runtime_config is not None and not requires_profile:
262
+ # The deps command installs the dependencies, which means they
263
+ # may not exist before deps runs and the following would raise a
264
+ # CompilationError.
265
+ runtime_config.load_dependencies()
285
266
 
286
267
  with track_run(task):
287
268
  results = task.run()
@@ -419,19 +400,17 @@ class DbtHook(BaseHook):
419
400
  default_stdout. As these are initialized by the CLI app, we need to
420
401
  initialize them here.
421
402
  """
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")
403
+ if DBT_INSTALLED_GTE_1_8:
404
+ from dbt.events.logging import setup_event_logger
430
405
  else:
431
- from dbt.flags import get_flags
406
+ from dbt.events.functions import ( # type: ignore[no-redef]
407
+ setup_event_logger,
408
+ )
409
+
410
+ from dbt.flags import get_flags
432
411
 
433
- flags = get_flags()
434
- setup_event_logger(flags)
412
+ flags = get_flags()
413
+ setup_event_logger(flags)
435
414
 
436
415
  configured_file = logging.getLogger("configured_file")
437
416
  file_log = logging.getLogger("file_log")
@@ -458,7 +437,7 @@ class DbtHook(BaseHook):
458
437
  if not profiles_path.exists():
459
438
  profiles_path.parent.mkdir(exist_ok=True)
460
439
  with profiles_path.open("w", encoding="utf-8") as f:
461
- f.write("config:\n send_anonymous_usage_stats: false\n")
440
+ f.write("flags:\n send_anonymous_usage_stats: false\n")
462
441
 
463
442
  def get_dbt_target_from_connection(
464
443
  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
@@ -18,8 +18,9 @@ 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 ManifestLoader, 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
@@ -27,7 +28,6 @@ from dbt.task.compile import CompileTask
27
28
  from dbt.task.debug import DebugTask
28
29
  from dbt.task.deps import DepsTask
29
30
  from dbt.task.freshness import FreshnessTask
30
- from dbt.task.generate import GenerateTask
31
31
  from dbt.task.list import ListTask
32
32
  from dbt.task.run import RunTask
33
33
  from dbt.task.run_operation import RunOperationTask
@@ -35,38 +35,18 @@ from dbt.task.runnable import GraphRunnableTask
35
35
  from dbt.task.seed import SeedTask
36
36
  from dbt.task.snapshot import SnapshotTask
37
37
  from dbt.task.test import TestTask
38
- from dbt.tracking import initialize_from_flags as orig_initialize_from_flags
38
+ from dbt.tracking import initialize_from_flags
39
39
 
40
40
  from airflow_dbt_python.utils.enums import FromStrEnum, LogFormat, Output
41
41
  from airflow_dbt_python.utils.version import (
42
- DBT_INSTALLED_GTE_1_5,
43
42
  DBT_INSTALLED_GTE_1_7,
44
- DBT_INSTALLED_LESS_THAN_1_5,
43
+ DBT_INSTALLED_GTE_1_8,
45
44
  )
46
45
 
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
-
46
+ if DBT_INSTALLED_GTE_1_8:
47
+ from dbt.task.docs.generate import GenerateTask
65
48
  else:
66
- from dbt.parser.manifest import ManifestLoader
67
-
68
- ParseTask = CompileTask
69
- initialize_from_flags = orig_initialize_from_flags
49
+ from dbt.task.generate import GenerateTask # type: ignore[no-redef]
70
50
 
71
51
 
72
52
  def parse_yaml_args(args: Optional[Union[str, dict[str, Any]]]) -> dict[str, Any]:
@@ -108,16 +88,16 @@ class BaseConfig:
108
88
  threads: Optional[int] = None
109
89
  use_experimental_parser: Optional[bool] = None
110
90
  store_failures: Optional[bool] = None
111
- vars: Optional[Union[str, dict[str, Any]]] = None
91
+ vars: Union[dict[str, Any]] = dataclasses.field(default_factory=dict)
112
92
  macro_debugging: Optional[bool] = None
113
93
  warn_error: Optional[bool] = None
114
94
  warn_error_options: dict[str, Any] = dataclasses.field(default_factory=dict)
115
95
  indirect_selection: str = "eager"
116
96
 
117
97
  # 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"
98
+ log_format: LogFormat = LogFormat.DEFAULT
99
+ log_format_file: LogFormat = LogFormat.DEFAULT
100
+ log_path: Optional[str] = "logs"
121
101
  log_level: str = "info"
122
102
  log_level_file: str = "none"
123
103
  record_timing_info: Optional[str] = None
@@ -154,22 +134,11 @@ class BaseConfig:
154
134
  version_check: Optional[bool] = None
155
135
  no_version_check: Optional[bool] = dataclasses.field(default=None, repr=False)
156
136
 
157
- # dbt < 1.5
158
- anonymous_usage_stats: Optional[bool] = None
159
- no_anonymous_usage_stats: Optional[bool] = dataclasses.field(
137
+ send_anonymous_usage_stats: Optional[bool] = None
138
+ no_send_anonymous_usage_stats: Optional[bool] = dataclasses.field(
160
139
  default=None, repr=False
161
140
  )
162
141
 
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)
172
-
173
142
  # dbt >= 1.7
174
143
  if DBT_INSTALLED_GTE_1_7:
175
144
  from dbt.cli.option_types import Package
@@ -184,21 +153,26 @@ class BaseConfig:
184
153
  default=None, repr=False
185
154
  )
186
155
 
156
+ require_resource_names_without_spaces: bool = False
157
+
187
158
  add_package: Optional[Package] = None
188
159
  dry_run: bool = False
189
160
  lock: bool = False
190
161
  static: bool = False
191
162
  upgrade: bool = False
192
163
 
164
+ if DBT_INSTALLED_GTE_1_8:
165
+ require_model_names_without_spaces: bool = False
166
+ source_freshness_run_project_hooks: bool = False
167
+ exclude_resource_types: list[str] = dataclasses.field(
168
+ default_factory=list, repr=False
169
+ )
170
+
193
171
  def __post_init__(self):
194
172
  """Post initialization actions for a dbt configuration."""
195
- self.parsed_vars = parse_yaml_args(self.vars)
196
- self.vars = yaml.dump(self.parsed_vars)
173
+ self.vars = parse_yaml_args(self.vars)
197
174
  self.set_mutually_exclusive_attributes()
198
175
 
199
- self.send_anonymous_usage_stats = self.anonymous_usage_stats
200
- self.no_send_anonymous_usage_stats = self.no_anonymous_usage_stats
201
-
202
176
  def set_mutually_exclusive_attributes(self):
203
177
  """Support pairs of mutually exclusive parameters.
204
178
 
@@ -219,7 +193,6 @@ class BaseConfig:
219
193
  ("populate_cache", True),
220
194
  ("static_parser", True),
221
195
  ("version_check", True),
222
- ("anonymous_usage_stats", True),
223
196
  ("send_anonymous_usage_stats", True),
224
197
  ("write_json", True),
225
198
  ("include_saved_query", None),
@@ -252,40 +225,6 @@ class BaseConfig:
252
225
  item = item.lower()
253
226
  return super().__getattribute__(item)
254
227
 
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
228
  @property
290
229
  def dbt_task(self) -> Type[BaseTask]:
291
230
  """Access to the underlying dbt task class."""
@@ -376,30 +315,56 @@ class BaseConfig:
376
315
  A tuple with the corresponding subclass of a dbt BaseTask and the
377
316
  RuntimeConfig for the task.
378
317
  """
379
- if not DBT_INSTALLED_LESS_THAN_1_5:
380
- local_flags = self.to_flags()
381
- flags.set_flags(local_flags)
382
-
318
+ flags.set_from_args(self, {}) # type: ignore
383
319
  project, profile = self.create_dbt_project_and_profile(extra_targets)
320
+
321
+ self.cls.set_log_format()
322
+ initialize_from_flags(self.send_anonymous_usage_stats, self.profiles_dir)
323
+
324
+ local_flags = flags.get_flags()
325
+
384
326
  runtime_config = self.create_runtime_config(project, profile)
385
327
 
386
- if not DBT_INSTALLED_LESS_THAN_1_5:
328
+ if DBT_INSTALLED_GTE_1_8:
329
+ if issubclass(self.dbt_task, ConfiguredTask) and runtime_config:
330
+ manifest = parse_manifest(
331
+ runtime_config,
332
+ write_perf_info=write_perf_info,
333
+ write=False,
334
+ write_json=False,
335
+ )
336
+ task: BaseTask = self.dbt_task(
337
+ args=local_flags, config=runtime_config, manifest=manifest
338
+ )
339
+ elif issubclass(self.dbt_task, DepsTask):
340
+ task = self.dbt_task(args=local_flags, project=project)
341
+ elif issubclass(self.dbt_task, DebugTask):
342
+ task = self.dbt_task(args=local_flags)
343
+ elif issubclass(self.dbt_task, CleanTask):
344
+ task = self.dbt_task(args=local_flags, config=project)
345
+ else:
346
+ task = self.dbt_task(args=local_flags)
347
+ else:
387
348
  if issubclass(self.dbt_task, ConfiguredTask) and runtime_config:
388
- register_adapter(runtime_config)
349
+ register_adapter(runtime_config) # type: ignore[call-arg]
389
350
  manifest = ManifestLoader.get_full_manifest(
390
351
  runtime_config, write_perf_info=write_perf_info
391
352
  )
392
- task: BaseTask = self.dbt_task(
393
- args=self, config=runtime_config, manifest=manifest
353
+ task = self.dbt_task(
354
+ args=self, # type: ignore[arg-type]
355
+ config=runtime_config,
356
+ manifest=manifest,
394
357
  )
395
358
  elif issubclass(self.dbt_task, DepsTask):
396
359
  task = self.dbt_task(args=self, project=project)
397
360
  elif issubclass(self.dbt_task, DebugTask):
398
- task = self.dbt_task(args=self, config=runtime_config)
361
+ task = self.dbt_task(args=self, config=runtime_config) # type: ignore[arg-type,call-arg]
399
362
  else:
400
- task = self.dbt_task(args=self, config=runtime_config, project=project)
401
- else:
402
- task = self.dbt_task(args=self, config=runtime_config)
363
+ task = self.dbt_task( # type: ignore[call-arg]
364
+ args=self, # type: ignore[arg-type]
365
+ config=runtime_config,
366
+ project=project,
367
+ )
403
368
 
404
369
  if self.compiled_target is not None:
405
370
  # Only supported by subclasses of dbt's GraphRunnableTask.
@@ -415,7 +380,6 @@ class BaseConfig:
415
380
 
416
381
  If the given task's ConfigType is not RuntimeConfig, None will be returned
417
382
  instead.
418
- Flags are also initialized as configuration is created.
419
383
 
420
384
  Args:
421
385
  project: dbt project.
@@ -424,17 +388,13 @@ class BaseConfig:
424
388
  Returns:
425
389
  A RuntimeConfig instance.
426
390
  """
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
391
+ if isinstance(self.vars, str):
392
+ self.vars = yaml.safe_load(self.vars)
432
393
 
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
394
+ try:
395
+ return RuntimeConfig.from_parts(project=project, profile=profile, args=self)
396
+ except DbtProjectError:
397
+ return None
438
398
 
439
399
  def create_dbt_project_and_profile(
440
400
  self, extra_targets: Optional[dict[str, Any]] = None
@@ -449,7 +409,7 @@ class BaseConfig:
449
409
  """
450
410
  profile = self.create_dbt_profile(extra_targets)
451
411
 
452
- project_renderer = DbtProjectYamlRenderer(profile, self.parsed_vars)
412
+ project_renderer = DbtProjectYamlRenderer(profile, self.vars)
453
413
  project = Project.from_project_root(
454
414
  self.project_dir or ".",
455
415
  project_renderer,
@@ -480,7 +440,7 @@ class BaseConfig:
480
440
  @property
481
441
  def profile_renderer(self) -> ProfileRenderer:
482
442
  """Return a ProfileRenderer with this config's parsed vars."""
483
- profile_renderer = ProfileRenderer(self.parsed_vars)
443
+ profile_renderer = ProfileRenderer(self.vars)
484
444
  return profile_renderer
485
445
 
486
446
  @property
@@ -513,6 +473,12 @@ class BaseConfig:
513
473
  Returns:
514
474
  A dbt profile for the task represented by this configuration.
515
475
  """
476
+ if DBT_INSTALLED_GTE_1_8:
477
+ from dbt_common.clients.system import get_env
478
+ from dbt_common.context import set_invocation_context
479
+
480
+ set_invocation_context(get_env())
481
+
516
482
  if self.profiles_dir is not None:
517
483
  raw_profiles = read_profile(self.profiles_dir)
518
484
  else:
@@ -524,15 +490,12 @@ class BaseConfig:
524
490
  outputs.setdefault("target", self.target)
525
491
  profile["outputs"] = {**outputs, **extra_targets}
526
492
 
527
- user_config = raw_profiles.get("config", {})
528
-
529
493
  profile = Profile.from_raw_profile_info(
530
494
  raw_profile=raw_profiles.get(
531
495
  self.profile_name, {}
532
496
  ), # Let dbt handle missing profile errors.
533
497
  profile_name=self.profile_name,
534
498
  renderer=self.profile_renderer,
535
- user_config=user_config,
536
499
  target_override=self.target,
537
500
  threads_override=self.threads,
538
501
  )
@@ -668,7 +631,7 @@ class ParseTaskConfig(SelectionConfig):
668
631
  TODO: Temporarily, handle this as a CompileTask.
669
632
  """
670
633
 
671
- cls: Type[BaseTask] = dataclasses.field(default=ParseTask, init=False)
634
+ cls: Type[BaseTask] = dataclasses.field(default=CompileTask, init=False)
672
635
  compile: Optional[bool] = None
673
636
  which: str = dataclasses.field(default="parse", init=False)
674
637
  write_manifest: Optional[bool] = None
@@ -694,8 +657,6 @@ class RunOperationTaskConfig(BaseConfig):
694
657
  def __post_init__(self):
695
658
  """Support dictionary args by casting them to str after setting."""
696
659
  super().__post_init__()
697
- if DBT_INSTALLED_LESS_THAN_1_5:
698
- self.args = str(self.args)
699
660
 
700
661
 
701
662
  @dataclasses.dataclass
@@ -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
@@ -3,28 +3,25 @@
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
13
  DBT_1_7 = VersionSpecifier(
16
14
  major="1", minor="7", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
17
15
  )
18
16
  DBT_1_8 = VersionSpecifier(
19
17
  major="1", minor="8", patch="0", matcher=Matchers.GREATER_THAN_OR_EQUAL
20
18
  )
19
+ DBT_1_9 = VersionSpecifier(
20
+ major="1", minor="9", 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
23
  DBT_INSTALLED_GTE_1_7 = installed.compare(DBT_1_7) == 1
24
+ DBT_INSTALLED_GTE_1_8 = installed.compare(DBT_1_8) == 1
27
25
 
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
26
  DBT_INSTALLED_1_7 = DBT_1_7 < installed < DBT_1_8
27
+ DBT_INSTALLED_1_8 = DBT_1_8 < installed < DBT_1_9
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "airflow-dbt-python"
3
- version = "2.0.1"
3
+ version = "2.1.0"
4
4
  description = "A collection of Airflow operators, hooks, and utilities to execute dbt commands"
5
5
  authors = ["Tomás Farías Santana <tomas@tomasfarias.dev>"]
6
6
  license = "MIT"
@@ -17,42 +17,25 @@ classifiers = [
17
17
  "Programming Language :: Python :: 3.9",
18
18
  "Programming Language :: Python :: 3.10",
19
19
  "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
20
21
  ]
21
22
 
22
23
  [tool.poetry.dependencies]
23
- python = ">=3.8,<3.12" # Airflow providers require this upper boundary
24
+ python = ">=3.8,<3.13" # Airflow providers require this upper boundary
24
25
 
25
- apache-airflow = { version = ">=2.2" }
26
+ apache-airflow = { version = ">=2.7" }
26
27
  apache-airflow-providers-amazon = { version = ">=3.0.0", optional = true }
27
28
  apache-airflow-providers-ssh = { version = ">=3.0.0", optional = true }
28
29
  connexion = { version = "<3.0" } # https://github.com/apache/airflow/issues/35217
29
30
  dulwich = { version = ">=0.21", optional = true }
30
31
  contextlib-chdir = { version = "==1.0.2", python = "<3.11" }
31
32
 
32
- dbt-core = [
33
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
34
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
35
- ]
36
- dbt-bigquery = [
37
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
38
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
39
- ]
40
- dbt-postgres = [
41
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
42
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
43
- ]
44
- dbt-redshift = [
45
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
46
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
47
- ]
48
- dbt-snowflake = [
49
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
50
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
51
- ]
52
- dbt-spark = [
53
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
54
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
55
- ]
33
+ dbt-core = { version = ">=1.7.5,<1.9.0", optional = true }
34
+ dbt-bigquery = { version = ">=1.7.0,<1.9.0", optional = true }
35
+ dbt-postgres = { version = ">=1.7.0,<1.9.0", optional = true }
36
+ dbt-redshift = { version = ">=1.7.0,<1.9.0", optional = true }
37
+ dbt-snowflake = { version = ">=1.7.0,<1.9.0", optional = true }
38
+ dbt-spark = { version = ">=1.7.0,<1.9.0", optional = true }
56
39
 
57
40
  [tool.poetry.group.dev]
58
41
  optional = true
@@ -63,15 +46,16 @@ apache-airflow-providers-ssh = ">=3.0.0"
63
46
  black = ">=22"
64
47
  boto3-stubs = { extras = ["s3"], version = ">=1.26.8" }
65
48
  coverage = { extras = ["toml"], version = ">=7.2" }
66
- dbt-postgres = ">=1.0.0,<1.8.0"
49
+ # docutils 0.21 causes an error with poetry (https://github.com/python-poetry/poetry/issues/9293)
50
+ docutils = "!=0.21"
67
51
  dulwich = ">=0.21,!=0.21.6"
68
52
  moto = ">=4.0.3"
69
53
  mypy = ">=1"
70
54
  freezegun = ">=1.1.0"
71
55
  pre-commit = ">=2.18"
72
- psycopg2-binary = ">=2.8.6"
56
+ psycopg = ">=3.0"
73
57
  pytest = ">=7.2"
74
- pytest-postgresql = ">=3,<4" # dbt-postgres doesn't support psycopg3 yet
58
+ pytest-postgresql = ">=5"
75
59
  ruff = ">=0.0.254"
76
60
  types-freezegun = ">=1.1.6"
77
61
  types-PyYAML = ">=6.0.7"
@@ -93,26 +77,11 @@ attrs = ">=22"
93
77
  optional = true
94
78
 
95
79
  [tool.poetry.group.adapters.dependencies]
96
- dbt-bigquery = [
97
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
98
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
99
- ]
100
- dbt-postgres = [
101
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
102
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
103
- ]
104
- dbt-redshift = [
105
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
106
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
107
- ]
108
- dbt-snowflake = [
109
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
110
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
111
- ]
112
- dbt-spark = [
113
- { python = "<3.8", version = ">=1.4.0,<1.6.0", optional = true },
114
- { python = ">=3.11", version = ">=1.4.0,<1.8.0", optional = true },
115
- ]
80
+ dbt-bigquery = { version = ">=1.7.0,<1.9.0", optional = true }
81
+ dbt-postgres = { version = ">=1.7.0,<1.9.0", optional = true }
82
+ dbt-redshift = { version = ">=1.7.0,<1.9.0", optional = true }
83
+ dbt-snowflake = { version = ">=1.7.0,<1.9.0", optional = true }
84
+ dbt-spark = { version = ">=1.7.0,<1.9.0", optional = true }
116
85
 
117
86
  [tool.poetry.extras]
118
87
  airflow-providers = ["apache-airflow-providers-amazon", "apache-airflow-providers-ssh"]
@@ -132,6 +101,7 @@ build-backend = "poetry.core.masonry.api"
132
101
 
133
102
  [tool.black]
134
103
  line-length = 88
104
+ target-version = ["py38", "py39", "py310", "py311", "py312"]
135
105
  include = '\.pyi?$'
136
106
  exclude = '''
137
107
  /(
@@ -180,10 +150,8 @@ markers = [
180
150
  ]
181
151
 
182
152
  [tool.ruff]
183
- select = ["E", "D", "F", "I"]
184
- ignore = ["D107"]
185
153
  line-length = 88
186
- target-version = "py311"
154
+ target-version = "py38"
187
155
  exclude = [
188
156
  ".bzr",
189
157
  ".direnv",
@@ -209,14 +177,18 @@ exclude = [
209
177
  "webserver_config.py",
210
178
  ]
211
179
 
212
- [tool.ruff.isort]
180
+ [tool.ruff.lint]
181
+ select = ["E", "D", "F", "I"]
182
+ ignore = ["D107"]
183
+
184
+ [tool.ruff.lint.isort]
213
185
  known-third-party = [
214
186
  "airflow",
215
187
  "dbt",
216
188
  "yaml",
217
189
  ]
218
190
 
219
- [tool.ruff.per-file-ignores]
191
+ [tool.ruff.lint.per-file-ignores]
220
192
  "__init__.py" = [
221
193
  "E402",
222
194
  "F401",
@@ -227,5 +199,5 @@ known-third-party = [
227
199
  ]
228
200
  "docs/*" = ["E402"]
229
201
 
230
- [tool.ruff.pydocstyle]
202
+ [tool.ruff.lint.pydocstyle]
231
203
  convention = "google"
@@ -0,0 +1,51 @@
1
+ # -*- coding: utf-8 -*-
2
+ from setuptools import setup
3
+
4
+ packages = \
5
+ ['airflow_dbt_python',
6
+ 'airflow_dbt_python.hooks',
7
+ 'airflow_dbt_python.operators',
8
+ 'airflow_dbt_python.utils']
9
+
10
+ package_data = \
11
+ {'': ['*']}
12
+
13
+ install_requires = \
14
+ ['apache-airflow>=2.7', 'connexion<3.0']
15
+
16
+ extras_require = \
17
+ {':python_version < "3.11"': ['contextlib-chdir==1.0.2'],
18
+ 'adapters': ['dbt-bigquery>=1.7.0,<1.9.0',
19
+ 'dbt-postgres>=1.7.0,<1.9.0',
20
+ 'dbt-redshift>=1.7.0,<1.9.0',
21
+ 'dbt-snowflake>=1.7.0,<1.9.0',
22
+ 'dbt-spark>=1.7.0,<1.9.0'],
23
+ 'airflow-providers': ['apache-airflow-providers-amazon>=3.0.0',
24
+ 'apache-airflow-providers-ssh>=3.0.0'],
25
+ 'bigquery': ['dbt-bigquery>=1.7.0,<1.9.0'],
26
+ 'git': ['apache-airflow-providers-ssh>=3.0.0', 'dulwich>=0.21'],
27
+ 'postgres': ['dbt-postgres>=1.7.0,<1.9.0'],
28
+ 'redshift': ['dbt-redshift>=1.7.0,<1.9.0'],
29
+ 's3': ['apache-airflow-providers-amazon>=3.0.0'],
30
+ 'snowflake': ['dbt-snowflake>=1.7.0,<1.9.0'],
31
+ 'spark': ['dbt-spark>=1.7.0,<1.9.0']}
32
+
33
+ setup_kwargs = {
34
+ 'name': 'airflow-dbt-python',
35
+ 'version': '2.1.0',
36
+ 'description': 'A collection of Airflow operators, hooks, and utilities to execute dbt commands',
37
+ 'long_description': '# airflow-dbt-python\n\n[![PyPI version](https://img.shields.io/pypi/v/airflow-dbt-python?style=plastic)](https://pypi.org/project/airflow-dbt-python/)\n[![CI testing](https://github.com/tomasfarias/airflow-dbt-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Test coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomasfarias/81ef37701aa088d18db8a58ce07c79c7/raw/covbadge.json)](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[![Documentation](https://readthedocs.org/projects/airflow-dbt-python/badge/?version=latest)](https://airflow-dbt-python.readthedocs.io/en/latest/?badge=latest)\n\nA collection of [Airflow](https://airflow.apache.org/) operators, hooks, and utilities to execute [`dbt`](https://pypi.org/project/dbt-core/) commands.\n\nRead the [documentation](https://airflow-dbt-python.readthedocs.io) for examples, installation instructions, and more details.\n\n# Installation\n\n## Requirements\n\nBefore using *airflow-dbt-python*, ensure you meet the following requirements:\n* A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.7.5 or later.\n* An Airflow environment using version 2.7 or later.\n\n * 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.\n * If self-hosting, Airflow installation instructions can be found in their [official documentation](https://airflow.apache.org/docs/apache-airflow/stable/installation/index.html).\n\n* Running Python 3.8 or later in your Airflow environment.\n\n> **Warning**\n>\n> Even though we don\'t impose any upper limits on versions of Airflow and *dbt*, it\'s possible that new versions are not supported immediately after release, particularly for *dbt*. We recommend testing the latest versions before upgrading and [reporting any issues](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose).\n\n> **Note**\n>\n> 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/).\n\n## From PyPI\n\n*airflow-dbt-python* is available in [PyPI](https://pypi.org/project/airflow-dbt-python/) and can be installed with *pip*:\n\n``` shell\npip install airflow-dbt-python\n```\n\nAs a convenience, some *dbt* adapters can be installed by specifying extras. For example, if requiring the *dbt-redshift* adapter:\n\n``` shell\npip install airflow-dbt-python[redshift]\n```\n\n## From this repo\n\n*airflow-dbt-python* can also be built from source by cloning this GitHub repository:\n\n``` shell\ngit clone https://github.com/tomasfarias/airflow-dbt-python.git\ncd airflow-dbt-python\n```\n\nAnd installing with *Poetry*:\n\n``` shell\npoetry install\n```\n\n## In AWS MWAA\n\nAdd *airflow-dbt-python* to your `requirements.txt` file and edit your Airflow environment to use this new `requirements.txt` file, or upload it as a plugin.\n\nRead 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.\n\n## In GCP Cloud Composer\n\nAdd *airflow-dbt-python* to your PyPI packages list.\n\nRead 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.\n\n## In other managed services\n\n*airflow-dbt-python* should be compatible with most or all Airflow managed services. Consult the documentation specific to your provider.\n\nIf you notice an issue when installing *airflow-dbt-python* in a specific managed service, please open an [issue](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose).\n\n# Features\n\n*airflow-dbt-python* aims to make dbt a **first-class citizen** of Airflow by supporting additional features that integrate both tools. As you would expect, *airflow-dbt-python* can run all your dbt workflows in Airflow with the same interface you are used to from the CLI, but without being a mere wrapper: *airflow-dbt-python* directly communicates with internal *dbt-core* classes, bridging the gap between them and Airflow\'s operator interface. Essentially, we are attempting to use *dbt* **as a library**.\n\nAs this integration was completed, several features were developed to **extend the capabilities of dbt** to leverage Airflow as much as possible. Can you think of a way *dbt* could leverage Airflow that is not currently supported? Let us know in a [GitHub issue](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose)!\n\n## Independent task execution\n\nAirflow executes [Tasks](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html) independent of one another: even though downstream and upstream dependencies between tasks exist, the execution of an individual task happens entirely independently of any other task execution (see: [Tasks Relationships](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html#relationships)).\n\nIn order to work with this constraint, *airflow-dbt-python* runs each dbt command in a **temporary and isolated directory**. Before execution, all the relevant dbt files are copied from supported backends, and after executing the command any artifacts are exported. This ensures dbt can work with any Airflow deployment, including most production deployments as they are usually running [Remote Executors](https://airflow.apache.org/docs/apache-airflow/stable/executor/index.html#executor-types) and do not guarantee any files will be shared by default between tasks, since each task may run in a completely different environment.\n\n\n## Download dbt files from a remote storage\n\nThe dbt parameters `profiles_dir` and `project_dir` would normally point to a directory containing a `profiles.yml` file and a dbt project in the local environment respectively (defined by the presence of a *dbt_project.yml* file). *airflow-dbt-python* extends these parameters to also accept an URL pointing to a remote storage.\n\nCurrently, we support the following remote storages:\n\n* [AWS S3](https://aws.amazon.com/s3/) (identified by a *s3* scheme).\n* Remote git repositories, like those stored in GitHub (both *https* and *ssh* schemes are supported).\n\n* If a remote URL is used for `project_dir`, then this URL must point to a location in your remote storage containing a *dbt* project to run. A *dbt* project is identified by the prescence of a *dbt_project.yml*, and contains all your [resources](https://docs.getdbt.com/docs/build/projects). All of the contents of this remote location will be downloaded and made available for the operator. The URL may also point to an archived file containing all the files of a dbt project, which will be downloaded, uncompressed, and made available for the operator.\n* If a remote URL is used for `profiles_dir`, then this URL must point to a location in your remote storage that contains a *profiles.yml* file. The *profiles.yml* file will be downloaded and made available for the operator to use when running. The *profiles.yml* may be part of your *dbt* project, in which case this argument may be ommitted.\n\nThis feature is intended to work in line with Airflow\'s [description of the task concept](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html#relationships):\n\n> Tasks don’t pass information to each other by default, and run entirely independently.\n\nWe interpret this as meaning a task should be responsible of fetching all the *dbt* related files it needs in order to run independently, as already described in [Independent Task Execution](#independent-task-execution).\n\n## Push dbt artifacts to XCom\n\nEach dbt execution produces one or more [JSON artifacts](https://docs.getdbt.com/reference/artifacts/dbt-artifacts/) that are valuable to produce meta-metrics, build conditional workflows, for reporting purposes, and other uses. *airflow-dbt-python* can push these artifacts to [XCom](https://airflow.apache.org/docs/apache-airflow/stable/concepts/xcoms.html) as requested via the `do_xcom_push_artifacts` parameter, which takes a list of artifacts to push.\n\n## Use Airflow connections as dbt targets (without a profiles.yml)\n\n[Airflow connections](https://airflow.apache.org/docs/apache-airflow/stable/howto/connection.html) allow users to manage and store connection information, such as hostname, port, username, and password, for operators to use when accessing certain applications, like databases. Similarly, a *dbt* `profiles.yml` file stores connection information under each target key. *airflow-dbt-python* bridges the gap between the two and allows you to use connection information stored as an Airflow connection by specifying the connection id as the `target` parameter of any of the *dbt* operators it provides. What\'s more, if using an Airflow connection, the `profiles.yml` file may be entirely omitted (although keep in mind a `profiles.yml` file contains a configuration block besides target connection information).\n\nSee an example DAG [here](examples/airflow_connection_target_dag.py).\n\n# Motivation\n\n## Airflow running in a managed environment\n\nAlthough [`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.\n\nThis 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.\n\nThere is a workaround which involves using Airflow\'s `BashOperator` and running Python from the command line:\n\n``` python\nfrom airflow.operators.bash import BashOperator\n\nBASH_COMMAND = "python -c \'from dbt.main import main; main()\' run"\noperator = BashOperator(\n task_id="dbt_run",\n bash_command=BASH_COMMAND,\n)\n```\n\nBut it can get cumbersome when appending all potential arguments a `dbt run` command (or other subcommand) can take.\n\nThat\'s where *airflow-dbt-python* comes in: it abstracts the complexity of interfacing with *dbt-core* and exposes one operator for each *dbt* subcommand that can be instantiated with all the corresponding arguments that the *dbt* CLI would take.\n\n## An alternative to *airflow-dbt* that works without the *dbt* CLI\n\nThe alternative [`airflow-dbt`](https://pypi.org/project/airflow-dbt/) package, by default, would not work if the *dbt* CLI is not in PATH, which means it would not be usable in MWAA. There is a workaround via the `dbt_bin` argument, which can be set to `"python -c \'from dbt.main import main; main()\' run"`, in similar fashion as the `BashOperator` example. Yet this approach is not without its limitations:\n* *airflow-dbt* works by wrapping the *dbt* CLI, which makes our code dependent on the environment in which it runs.\n* *airflow-dbt* does not support the full range of arguments a command can take. For example, `DbtRunOperator` does not have an attribute for `fail_fast`.\n* *airflow-dbt* does not offer access to *dbt* artifacts created during execution. *airflow-dbt-python* does so by pushing any artifacts to [XCom](https://airflow.apache.org/docs/apache-airflow/stable/concepts/xcoms.html).\n\n# Usage\n\nCurrently, the following *dbt* commands are supported:\n\n* `clean`\n* `compile`\n* `debug`\n* `deps`\n* `docs generate`\n* `ls`\n* `parse`\n* `run`\n* `run-operation`\n* `seed`\n* `snapshot`\n* `source`\n* `test`\n\n## Examples\n\nAll example DAGs are tested against the latest Airflow version. Some changes, like modifying `import` statements or changing types, may be required for them to work in other versions.\n\n``` python\nimport datetime as dt\n\nimport pendulum\nfrom airflow import DAG\n\nfrom airflow_dbt_python.operators.dbt import (\n DbtRunOperator,\n DbtSeedOperator,\n DbtTestOperator,\n)\n\nargs = {\n "owner": "airflow",\n}\n\nwith DAG(\n dag_id="example_dbt_operator",\n default_args=args,\n schedule="0 0 * * *",\n start_date=pendulum.today("UTC").add(days=-1),\n dagrun_timeout=dt.timedelta(minutes=60),\n tags=["example", "example2"],\n) as dag:\n dbt_test = DbtTestOperator(\n task_id="dbt_test",\n selector_name="pre-run-tests",\n )\n\n dbt_seed = DbtSeedOperator(\n task_id="dbt_seed",\n select=["/path/to/first.csv", "/path/to/second.csv"],\n full_refresh=True,\n )\n\n dbt_run = DbtRunOperator(\n task_id="dbt_run",\n select=["/path/to/models"],\n full_refresh=True,\n fail_fast=True,\n )\n\n dbt_test >> dbt_seed >> dbt_run\n```\n\nMore examples can be found in the [`examples/`](examples/) directory and the [documentation](https://airflow-dbt-python.readthedocs.io).\n\n# Development\n\nSee the [development documentation](https://airflow-dbt-python.readthedocs.io/en/latest/development.html) for a more in-depth dive into setting up a development environment, running the test-suite, and general commentary on working on *airflow-dbt-python*.\n\n## Testing\n\nTests are run with *pytest*, can be located in `tests/`. To run them locally, you may use *Poetry*:\n\n``` shell\npoetry run pytest tests/ -vv\n```\n\n# License\n\nThis project is licensed under the MIT license. See [LICENSE](LICENSE).\n',
38
+ 'author': 'Tomás Farías Santana',
39
+ 'author_email': 'tomas@tomasfarias.dev',
40
+ 'maintainer': 'None',
41
+ 'maintainer_email': 'None',
42
+ 'url': 'https://github.com/tomasfarias/airflow-dbt-python',
43
+ 'packages': packages,
44
+ 'package_data': package_data,
45
+ 'install_requires': install_requires,
46
+ 'extras_require': extras_require,
47
+ 'python_requires': '>=3.8,<3.13',
48
+ }
49
+
50
+
51
+ setup(**setup_kwargs)
@@ -1,81 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['airflow_dbt_python',
6
- 'airflow_dbt_python.hooks',
7
- 'airflow_dbt_python.operators',
8
- 'airflow_dbt_python.utils']
9
-
10
- package_data = \
11
- {'': ['*']}
12
-
13
- install_requires = \
14
- ['apache-airflow>=2.2', 'connexion<3.0']
15
-
16
- extras_require = \
17
- {':python_version < "3.11"': ['contextlib-chdir==1.0.2'],
18
- 'adapters:python_version < "3.8"': ['dbt-bigquery>=1.4.0,<1.6.0',
19
- 'dbt-bigquery>=1.4.0,<1.6.0',
20
- 'dbt-postgres>=1.4.0,<1.6.0',
21
- 'dbt-postgres>=1.4.0,<1.6.0',
22
- 'dbt-redshift>=1.4.0,<1.6.0',
23
- 'dbt-redshift>=1.4.0,<1.6.0',
24
- 'dbt-snowflake>=1.4.0,<1.6.0',
25
- 'dbt-snowflake>=1.4.0,<1.6.0',
26
- 'dbt-spark>=1.4.0,<1.6.0',
27
- 'dbt-spark>=1.4.0,<1.6.0'],
28
- 'adapters:python_version >= "3.11"': ['dbt-bigquery>=1.4.0,<1.8.0',
29
- 'dbt-bigquery>=1.4.0,<1.8.0',
30
- 'dbt-postgres>=1.4.0,<1.8.0',
31
- 'dbt-postgres>=1.4.0,<1.8.0',
32
- 'dbt-redshift>=1.4.0,<1.8.0',
33
- 'dbt-redshift>=1.4.0,<1.8.0',
34
- 'dbt-snowflake>=1.4.0,<1.8.0',
35
- 'dbt-snowflake>=1.4.0,<1.8.0',
36
- 'dbt-spark>=1.4.0,<1.8.0',
37
- 'dbt-spark>=1.4.0,<1.8.0'],
38
- 'airflow-providers': ['apache-airflow-providers-amazon>=3.0.0',
39
- 'apache-airflow-providers-ssh>=3.0.0'],
40
- 'bigquery:python_version < "3.8"': ['dbt-bigquery>=1.4.0,<1.6.0',
41
- 'dbt-bigquery>=1.4.0,<1.6.0'],
42
- 'bigquery:python_version >= "3.11"': ['dbt-bigquery>=1.4.0,<1.8.0',
43
- 'dbt-bigquery>=1.4.0,<1.8.0'],
44
- 'git': ['apache-airflow-providers-ssh>=3.0.0', 'dulwich>=0.21'],
45
- 'postgres:python_version < "3.8"': ['dbt-postgres>=1.4.0,<1.6.0',
46
- 'dbt-postgres>=1.4.0,<1.6.0'],
47
- 'postgres:python_version >= "3.11"': ['dbt-postgres>=1.4.0,<1.8.0',
48
- 'dbt-postgres>=1.4.0,<1.8.0'],
49
- 'redshift:python_version < "3.8"': ['dbt-redshift>=1.4.0,<1.6.0',
50
- 'dbt-redshift>=1.4.0,<1.6.0'],
51
- 'redshift:python_version >= "3.11"': ['dbt-redshift>=1.4.0,<1.8.0',
52
- 'dbt-redshift>=1.4.0,<1.8.0'],
53
- 's3': ['apache-airflow-providers-amazon>=3.0.0'],
54
- 'snowflake:python_version < "3.8"': ['dbt-snowflake>=1.4.0,<1.6.0',
55
- 'dbt-snowflake>=1.4.0,<1.6.0'],
56
- 'snowflake:python_version >= "3.11"': ['dbt-snowflake>=1.4.0,<1.8.0',
57
- 'dbt-snowflake>=1.4.0,<1.8.0'],
58
- 'spark:python_version < "3.8"': ['dbt-spark>=1.4.0,<1.6.0',
59
- 'dbt-spark>=1.4.0,<1.6.0'],
60
- 'spark:python_version >= "3.11"': ['dbt-spark>=1.4.0,<1.8.0',
61
- 'dbt-spark>=1.4.0,<1.8.0']}
62
-
63
- setup_kwargs = {
64
- 'name': 'airflow-dbt-python',
65
- 'version': '2.0.1',
66
- 'description': 'A collection of Airflow operators, hooks, and utilities to execute dbt commands',
67
- 'long_description': '# airflow-dbt-python\n\n[![PyPI version](https://img.shields.io/pypi/v/airflow-dbt-python?style=plastic)](https://pypi.org/project/airflow-dbt-python/)\n[![CI testing](https://github.com/tomasfarias/airflow-dbt-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Test coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tomasfarias/81ef37701aa088d18db8a58ce07c79c7/raw/covbadge.json)](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[![Documentation](https://readthedocs.org/projects/airflow-dbt-python/badge/?version=latest)](https://airflow-dbt-python.readthedocs.io/en/latest/?badge=latest)\n\nA collection of [Airflow](https://airflow.apache.org/) operators, hooks, and utilities to execute [`dbt`](https://pypi.org/project/dbt-core/) commands.\n\nRead the [documentation](https://airflow-dbt-python.readthedocs.io) for examples, installation instructions, and more details.\n\n# Installation\n\n## Requirements\n\nBefore using *airflow-dbt-python*, ensure you meet the following requirements:\n* A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.4.0 or later.\n* An Airflow environment using version 2.2 or later.\n\n * If using any managed service, like AWS MWAA, ensure your environment is created with a supported version of Airflow.\n * If self-hosting, Airflow installation instructions can be found in their [official documentation](https://airflow.apache.org/docs/apache-airflow/stable/installation/index.html).\n\n* Running Python 3.8 or later in your Airflow environment.\n\n> **Warning**\n>\n> Even though we don\'t impose any upper limits on versions of Airflow and *dbt*, it\'s possible that new versions are not supported immediately after release, particularly for *dbt*. We recommend testing the latest versions before upgrading and [reporting any issues](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose).\n\n> **Note**\n>\n> 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/).\n\n## From PyPI\n\n*airflow-dbt-python* is available in [PyPI](https://pypi.org/project/airflow-dbt-python/) and can be installed with *pip*:\n\n``` shell\npip install airflow-dbt-python\n```\n\nAs a convenience, some *dbt* adapters can be installed by specifying extras. For example, if requiring the *dbt-redshift* adapter:\n\n``` shell\npip install airflow-dbt-python[redshift]\n```\n\n## From this repo\n\n*airflow-dbt-python* can also be built from source by cloning this GitHub repository:\n\n``` shell\ngit clone https://github.com/tomasfarias/airflow-dbt-python.git\ncd airflow-dbt-python\n```\n\nAnd installing with *Poetry*:\n\n``` shell\npoetry install\n```\n\n## In AWS MWAA\n\nAdd *airflow-dbt-python* to your `requirements.txt` file and edit your Airflow environment to use this new `requirements.txt` file, or upload it as a plugin.\n\nRead 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.\n\n## In other managed services\n\n*airflow-dbt-python* should be compatible with most or all Airflow managed services. Consult the documentation specific to your provider.\n\nIf you notice an issue when installing *airflow-dbt-python* in a specific managed service, please open an [issue](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose).\n\n# Features\n\n*airflow-dbt-python* aims to make dbt a **first-class citizen** of Airflow by supporting additional features that integrate both tools. As you would expect, *airflow-dbt-python* can run all your dbt workflows in Airflow with the same interface you are used to from the CLI, but without being a mere wrapper: *airflow-dbt-python* directly communicates with internal *dbt-core* classes, bridging the gap between them and Airflow\'s operator interface. Essentially, we are attempting to use *dbt* **as a library**.\n\nAs this integration was completed, several features were developed to **extend the capabilities of dbt** to leverage Airflow as much as possible. Can you think of a way *dbt* could leverage Airflow that is not currently supported? Let us know in a [GitHub issue](https://github.com/tomasfarias/airflow-dbt-python/issues/new/choose)!\n\n## Independent task execution\n\nAirflow executes [Tasks](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html) independent of one another: even though downstream and upstream dependencies between tasks exist, the execution of an individual task happens entirely independently of any other task execution (see: [Tasks Relationships](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html#relationships)).\n\nIn order to work with this constraint, *airflow-dbt-python* runs each dbt command in a **temporary and isolated directory**. Before execution, all the relevant dbt files are copied from supported backends, and after executing the command any artifacts are exported. This ensures dbt can work with any Airflow deployment, including most production deployments as they are usually running [Remote Executors](https://airflow.apache.org/docs/apache-airflow/stable/executor/index.html#executor-types) and do not guarantee any files will be shared by default between tasks, since each task may run in a completely different environment.\n\n\n## Download dbt files from a remote storage\n\nThe dbt parameters `profiles_dir` and `project_dir` would normally point to a directory containing a `profiles.yml` file and a dbt project in the local environment respectively (defined by the presence of a *dbt_project.yml* file). *airflow-dbt-python* extends these parameters to also accept an URL pointing to a remote storage.\n\nCurrently, we support the following remote storages:\n\n* [AWS S3](https://aws.amazon.com/s3/) (identified by a *s3* scheme).\n* Remote git repositories, like those stored in GitHub (both *https* and *ssh* schemes are supported).\n\n* If a remote URL is used for `project_dir`, then this URL must point to a location in your remote storage containing a *dbt* project to run. A *dbt* project is identified by the prescence of a *dbt_project.yml*, and contains all your [resources](https://docs.getdbt.com/docs/build/projects). All of the contents of this remote location will be downloaded and made available for the operator. The URL may also point to an archived file containing all the files of a dbt project, which will be downloaded, uncompressed, and made available for the operator.\n* If a remote URL is used for `profiles_dir`, then this URL must point to a location in your remote storage that contains a *profiles.yml* file. The *profiles.yml* file will be downloaded and made available for the operator to use when running. The *profiles.yml* may be part of your *dbt* project, in which case this argument may be ommitted.\n\nThis feature is intended to work in line with Airflow\'s [description of the task concept](https://airflow.apache.org/docs/apache-airflow/stable/concepts/tasks.html#relationships):\n\n> Tasks don’t pass information to each other by default, and run entirely independently.\n\nWe interpret this as meaning a task should be responsible of fetching all the *dbt* related files it needs in order to run independently, as already described in [Independent Task Execution](#independent-task-execution).\n\n## Push dbt artifacts to XCom\n\nEach dbt execution produces one or more [JSON artifacts](https://docs.getdbt.com/reference/artifacts/dbt-artifacts/) that are valuable to produce meta-metrics, build conditional workflows, for reporting purposes, and other uses. *airflow-dbt-python* can push these artifacts to [XCom](https://airflow.apache.org/docs/apache-airflow/stable/concepts/xcoms.html) as requested via the `do_xcom_push_artifacts` parameter, which takes a list of artifacts to push.\n\n## Use Airflow connections as dbt targets (without a profiles.yml)\n\n[Airflow connections](https://airflow.apache.org/docs/apache-airflow/stable/howto/connection.html) allow users to manage and store connection information, such as hostname, port, username, and password, for operators to use when accessing certain applications, like databases. Similarly, a *dbt* `profiles.yml` file stores connection information under each target key. *airflow-dbt-python* bridges the gap between the two and allows you to use connection information stored as an Airflow connection by specifying the connection id as the `target` parameter of any of the *dbt* operators it provides. What\'s more, if using an Airflow connection, the `profiles.yml` file may be entirely omitted (although keep in mind a `profiles.yml` file contains a configuration block besides target connection information).\n\nSee an example DAG [here](examples/airflow_connection_target_dag.py).\n\n# Motivation\n\n## Airflow running in a managed environment\n\nAlthough [`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.\n\nThis 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.\n\nThere is a workaround which involves using Airflow\'s `BashOperator` and running Python from the command line:\n\n``` python\nfrom airflow.operators.bash import BashOperator\n\nBASH_COMMAND = "python -c \'from dbt.main import main; main()\' run"\noperator = BashOperator(\n task_id="dbt_run",\n bash_command=BASH_COMMAND,\n)\n```\n\nBut it can get cumbersome when appending all potential arguments a `dbt run` command (or other subcommand) can take.\n\nThat\'s where *airflow-dbt-python* comes in: it abstracts the complexity of interfacing with *dbt-core* and exposes one operator for each *dbt* subcommand that can be instantiated with all the corresponding arguments that the *dbt* CLI would take.\n\n## An alternative to *airflow-dbt* that works without the *dbt* CLI\n\nThe alternative [`airflow-dbt`](https://pypi.org/project/airflow-dbt/) package, by default, would not work if the *dbt* CLI is not in PATH, which means it would not be usable in MWAA. There is a workaround via the `dbt_bin` argument, which can be set to `"python -c \'from dbt.main import main; main()\' run"`, in similar fashion as the `BashOperator` example. Yet this approach is not without its limitations:\n* *airflow-dbt* works by wrapping the *dbt* CLI, which makes our code dependent on the environment in which it runs.\n* *airflow-dbt* does not support the full range of arguments a command can take. For example, `DbtRunOperator` does not have an attribute for `fail_fast`.\n* *airflow-dbt* does not offer access to *dbt* artifacts created during execution. *airflow-dbt-python* does so by pushing any artifacts to [XCom](https://airflow.apache.org/docs/apache-airflow/stable/concepts/xcoms.html).\n\n# Usage\n\nCurrently, the following *dbt* commands are supported:\n\n* `clean`\n* `compile`\n* `debug`\n* `deps`\n* `docs generate`\n* `ls`\n* `parse`\n* `run`\n* `run-operation`\n* `seed`\n* `snapshot`\n* `source`\n* `test`\n\n## Examples\n\nAll example DAGs are tested against the latest Airflow version. Some changes, like modifying `import` statements or changing types, may be required for them to work in other versions.\n\n``` python\nimport datetime as dt\n\nimport pendulum\nfrom airflow import DAG\n\nfrom airflow_dbt_python.operators.dbt import (\n DbtRunOperator,\n DbtSeedOperator,\n DbtTestOperator,\n)\n\nargs = {\n "owner": "airflow",\n}\n\nwith DAG(\n dag_id="example_dbt_operator",\n default_args=args,\n schedule="0 0 * * *",\n start_date=pendulum.today("UTC").add(days=-1),\n dagrun_timeout=dt.timedelta(minutes=60),\n tags=["example", "example2"],\n) as dag:\n dbt_test = DbtTestOperator(\n task_id="dbt_test",\n selector_name="pre-run-tests",\n )\n\n dbt_seed = DbtSeedOperator(\n task_id="dbt_seed",\n select=["/path/to/first.csv", "/path/to/second.csv"],\n full_refresh=True,\n )\n\n dbt_run = DbtRunOperator(\n task_id="dbt_run",\n select=["/path/to/models"],\n full_refresh=True,\n fail_fast=True,\n )\n\n dbt_test >> dbt_seed >> dbt_run\n```\n\nMore examples can be found in the [`examples/`](examples/) directory and the [documentation](https://airflow-dbt-python.readthedocs.io).\n\n# Development\n\nSee the [development documentation](https://airflow-dbt-python.readthedocs.io/en/latest/development.html) for a more in-depth dive into setting up a development environment, running the test-suite, and general commentary on working on *airflow-dbt-python*.\n\n## Testing\n\nTests are run with *pytest*, can be located in `tests/`. To run them locally, you may use *Poetry*:\n\n``` shell\npoetry run pytest tests/ -vv\n```\n\n# License\n\nThis project is licensed under the MIT license. See [LICENSE](LICENSE).\n',
68
- 'author': 'Tomás Farías Santana',
69
- 'author_email': 'tomas@tomasfarias.dev',
70
- 'maintainer': 'None',
71
- 'maintainer_email': 'None',
72
- 'url': 'https://github.com/tomasfarias/airflow-dbt-python',
73
- 'packages': packages,
74
- 'package_data': package_data,
75
- 'install_requires': install_requires,
76
- 'extras_require': extras_require,
77
- 'python_requires': '>=3.8,<3.12',
78
- }
79
-
80
-
81
- setup(**setup_kwargs)