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.
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/PKG-INFO +21 -20
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/README.md +11 -5
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/__init__.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/__version__.py +2 -1
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/dbt.py +26 -47
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/git.py +2 -1
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/localfs.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/remote.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/s3.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/operators/dbt.py +11 -8
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/configs.py +77 -116
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/enums.py +2 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/env.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/url.py +1 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/utils/version.py +10 -13
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/pyproject.toml +28 -56
- airflow_dbt_python-2.1.0/setup.py +51 -0
- airflow_dbt_python-2.0.1/setup.py +0 -81
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/LICENSE +0 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/hooks/__init__.py +0 -0
- {airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/operators/__init__.py +0 -0
- {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
|
|
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.
|
|
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.
|
|
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.
|
|
38
|
-
Requires-Dist: dbt-
|
|
39
|
-
Requires-Dist: dbt-
|
|
40
|
-
Requires-Dist: dbt-
|
|
41
|
-
Requires-Dist: dbt-
|
|
42
|
-
Requires-Dist: dbt-
|
|
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.
|
|
71
|
-
* An Airflow environment using version 2.
|
|
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/)
|
|
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.
|
|
19
|
-
* An Airflow environment using version 2.
|
|
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/)
|
|
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,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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
434
|
-
|
|
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("
|
|
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.
|
|
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
|
)
|
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
97
|
-
|
|
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.
|
|
142
|
-
self.
|
|
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
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
43
|
+
DBT_INSTALLED_GTE_1_8,
|
|
45
44
|
)
|
|
46
45
|
|
|
47
|
-
if
|
|
48
|
-
|
|
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.
|
|
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:
|
|
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:
|
|
119
|
-
log_format_file:
|
|
120
|
-
log_path: Optional[str] =
|
|
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
|
-
|
|
158
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
393
|
-
args=self,
|
|
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(
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
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.
|
|
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.
|
|
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=
|
|
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
|
|
|
@@ -3,28 +3,25 @@
|
|
|
3
3
|
These are only used to ensure backwards compatibility with older versions of dbt.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
|
|
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
|
|
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.
|
|
24
|
+
python = ">=3.8,<3.13" # Airflow providers require this upper boundary
|
|
24
25
|
|
|
25
|
-
apache-airflow = { version = ">=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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
dbt-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
+
psycopg = ">=3.0"
|
|
73
57
|
pytest = ">=7.2"
|
|
74
|
-
pytest-postgresql = ">=
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
dbt-
|
|
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 = "
|
|
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.
|
|
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[](https://pypi.org/project/airflow-dbt-python/)\n[](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[](https://github.com/psf/black)\n[](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[](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[](https://pypi.org/project/airflow-dbt-python/)\n[](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[](https://github.com/psf/black)\n[](https://github.com/tomasfarias/airflow-dbt-python/actions)\n[](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)
|
|
File without changes
|
|
File without changes
|
{airflow_dbt_python-2.0.1 → airflow_dbt_python-2.1.0}/airflow_dbt_python/operators/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|