airflow-dbt-python 1.0.4__tar.gz → 2.0.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 (21) hide show
  1. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/PKG-INFO +20 -14
  2. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/README.md +2 -2
  3. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/__version__.py +1 -1
  4. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/dbt.py +78 -42
  5. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/git.py +15 -9
  6. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/operators/dbt.py +23 -2
  7. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/utils/configs.py +213 -46
  8. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/utils/url.py +41 -2
  9. airflow_dbt_python-2.0.0/airflow_dbt_python/utils/version.py +30 -0
  10. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/pyproject.toml +57 -20
  11. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/setup.py +46 -16
  12. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/LICENSE +0 -0
  13. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/__init__.py +0 -0
  14. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/__init__.py +0 -0
  15. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/localfs.py +0 -0
  16. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/remote.py +6 -6
  17. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/hooks/s3.py +0 -0
  18. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/operators/__init__.py +0 -0
  19. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/utils/__init__.py +0 -0
  20. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/utils/enums.py +0 -0
  21. {airflow_dbt_python-1.0.4 → airflow_dbt_python-2.0.0}/airflow_dbt_python/utils/env.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: airflow-dbt-python
3
- Version: 1.0.4
3
+ Version: 2.0.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.7.2,<4.0
9
+ Requires-Python: >=3.8,<3.12
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
@@ -17,11 +17,9 @@ 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.7
21
20
  Classifier: Programming Language :: Python :: 3.8
22
21
  Classifier: Programming Language :: Python :: 3.9
23
22
  Provides-Extra: adapters
24
- Provides-Extra: airflow
25
23
  Provides-Extra: airflow-providers
26
24
  Provides-Extra: bigquery
27
25
  Provides-Extra: docs
@@ -31,16 +29,24 @@ Provides-Extra: redshift
31
29
  Provides-Extra: s3
32
30
  Provides-Extra: snowflake
33
31
  Provides-Extra: spark
34
- Requires-Dist: apache-airflow (>=2.0) ; extra == "airflow"
32
+ Requires-Dist: apache-airflow (>=2.2)
35
33
  Requires-Dist: apache-airflow-providers-amazon (>=3.0.0) ; extra == "airflow-providers" or extra == "s3"
36
34
  Requires-Dist: apache-airflow-providers-ssh (>=3.0.0) ; extra == "airflow-providers" or extra == "git"
37
- Requires-Dist: dbt-bigquery (>=1.0.0) ; extra == "adapters" or extra == "bigquery"
38
- Requires-Dist: dbt-core (>=1.0.0)
39
- Requires-Dist: dbt-postgres (>=1.0.0) ; extra == "adapters" or extra == "postgres"
40
- Requires-Dist: dbt-redshift (>=1.0.0) ; extra == "adapters" or extra == "redshift"
41
- Requires-Dist: dbt-snowflake (>=1.0.0) ; extra == "adapters" or extra == "snowflake"
42
- Requires-Dist: dbt-spark (>=1.0.0) ; extra == "adapters" or extra == "spark"
43
- Requires-Dist: dulwich (>=0.20) ; extra == "git"
35
+ Requires-Dist: connexion (<3.0)
36
+ 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")
49
+ Requires-Dist: dulwich (>=0.21) ; extra == "git"
44
50
  Project-URL: Repository, https://github.com/tomasfarias/airflow-dbt-python
45
51
  Description-Content-Type: text/markdown
46
52
 
@@ -61,13 +67,13 @@ Read the [documentation](https://airflow-dbt-python.readthedocs.io) for examples
61
67
  ## Requirements
62
68
 
63
69
  Before using *airflow-dbt-python*, ensure you meet the following requirements:
64
- * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.0.0 or later.
70
+ * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.4.0 or later.
65
71
  * An Airflow environment using version 2.2 or later.
66
72
 
67
73
  * If using any managed service, like AWS MWAA, ensure your environment is created with a supported version of Airflow.
68
74
  * If self-hosting, Airflow installation instructions can be found in their [official documentation](https://airflow.apache.org/docs/apache-airflow/stable/installation/index.html).
69
75
 
70
- * Running Python 3.7 or later in your Airflow environment.
76
+ * Running Python 3.8 or later in your Airflow environment.
71
77
 
72
78
  > **Warning**
73
79
  >
@@ -15,13 +15,13 @@ 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.0.0 or later.
18
+ * A *dbt* project using [dbt-core](https://pypi.org/project/dbt-core/) version 1.4.0 or later.
19
19
  * An Airflow environment using version 2.2 or later.
20
20
 
21
21
  * If using any managed service, like AWS MWAA, 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
- * Running Python 3.7 or later in your Airflow environment.
24
+ * Running Python 3.8 or later in your Airflow environment.
25
25
 
26
26
  > **Warning**
27
27
  >
@@ -2,4 +2,4 @@
2
2
  __author__ = "Tomás Farías Santana"
3
3
  __copyright__ = "Copyright 2021 Tomás Farías Santana"
4
4
  __title__ = "airflow-dbt-python"
5
- __version__ = "1.0.4"
5
+ __version__ = "2.0.0"
@@ -24,6 +24,14 @@ from airflow.exceptions import AirflowException
24
24
  from airflow.hooks.base import BaseHook
25
25
  from airflow.models.connection import Connection
26
26
 
27
+ from airflow_dbt_python.utils.version import DBT_INSTALLED_LESS_THAN_1_5
28
+
29
+ if sys.version_info >= (3, 11):
30
+ from contextlib import chdir as chdir_ctx
31
+ else:
32
+ from contextlib_chdir import chdir as chdir_ctx
33
+
34
+
27
35
  if TYPE_CHECKING:
28
36
  from dbt.contracts.results import RunResult
29
37
  from dbt.task.base import BaseTask
@@ -208,6 +216,7 @@ class DbtHook(BaseHook):
208
216
  replace_on_upload: bool = False,
209
217
  artifacts: Optional[Iterable[str]] = None,
210
218
  env_vars: Optional[Dict[str, Any]] = None,
219
+ write_perf_info: bool = False,
211
220
  **kwargs,
212
221
  ) -> DbtTaskResult:
213
222
  """Run a dbt task with a given configuration and return the results.
@@ -219,9 +228,17 @@ class DbtHook(BaseHook):
219
228
  of running the dbt command.
220
229
  """
221
230
  from dbt.adapters.factory import register_adapter
222
- from dbt.config.runtime import UnsetProfileConfig
223
- from dbt.main import adapter_management, track_run
224
- from dbt.task.base import move_to_nearest_project_dir
231
+ from dbt.task.base import get_nearest_project_dir
232
+ from dbt.task.clean import CleanTask
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
225
242
 
226
243
  config = self.get_dbt_task_config(command, **kwargs)
227
244
  extra_target = self.get_dbt_target_from_connection(config.target)
@@ -233,53 +250,62 @@ class DbtHook(BaseHook):
233
250
  replace_on_upload=replace_on_upload,
234
251
  env_vars=env_vars,
235
252
  ) as dbt_dir:
236
- config.dbt_task.pre_init_hook(config)
237
- self.ensure_profiles(config.profiles_dir)
238
-
239
- task, runtime_config = config.create_dbt_task(extra_target)
240
-
241
253
  # When creating tasks via from_args, dbt switches to the project directory.
242
254
  # We have to do that here as we are not using from_args.
243
- move_to_nearest_project_dir(config)
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)
262
+
263
+ with chdir_ctx(nearest_project_dir):
264
+ config.dbt_task.pre_init_hook(config)
265
+ self.ensure_profiles(config)
244
266
 
245
- self.setup_dbt_logging(task, config.debug)
267
+ task, runtime_config = config.create_dbt_task(
268
+ extra_target, write_perf_info
269
+ )
270
+ requires_profile = isinstance(task, (CleanTask, DepsTask))
246
271
 
247
- if not isinstance(runtime_config, UnsetProfileConfig):
248
- if runtime_config is not None:
272
+ self.setup_dbt_logging(task, config.debug)
273
+
274
+ if runtime_config is not None and not requires_profile:
249
275
  # The deps command installs the dependencies, which means they may
250
276
  # not exist before deps runs and the following would raise a
251
277
  # CompilationError.
252
278
  runtime_config.load_dependencies()
253
279
 
254
- results = None
255
- with adapter_management():
256
- if not isinstance(runtime_config, UnsetProfileConfig):
257
- if runtime_config is not None:
258
- register_adapter(runtime_config)
280
+ results = None
281
+ with adapter_management():
282
+ if not requires_profile:
283
+ if runtime_config is not None:
284
+ register_adapter(runtime_config)
259
285
 
260
- with track_run(task):
261
- results = task.run()
262
- success = task.interpret_results(results)
286
+ with track_run(task):
287
+ results = task.run()
288
+ success = task.interpret_results(results)
263
289
 
264
- if artifacts is None:
265
- return DbtTaskResult(success, results, {})
290
+ if artifacts is None:
291
+ return DbtTaskResult(success, results, {})
266
292
 
267
- saved_artifacts = {}
268
- for artifact in artifacts:
269
- artifact_path = Path(dbt_dir) / "target" / artifact
293
+ saved_artifacts = {}
294
+ for artifact in artifacts:
295
+ artifact_path = Path(dbt_dir) / "target" / artifact
270
296
 
271
- if not artifact_path.exists():
272
- self.log.warn(
273
- "Required dbt artifact %s was not found. "
274
- "Perhaps dbt failed and couldn't generate it.",
275
- artifact,
276
- )
277
- continue
297
+ if not artifact_path.exists():
298
+ self.log.warning(
299
+ "Required dbt artifact %s was not found. "
300
+ "Perhaps dbt failed and couldn't generate it.",
301
+ artifact,
302
+ )
303
+ continue
278
304
 
279
- with open(artifact_path) as artifact_file:
280
- json_artifact = json.load(artifact_file)
305
+ with open(artifact_path) as artifact_file:
306
+ json_artifact = json.load(artifact_file)
281
307
 
282
- saved_artifacts[artifact] = json_artifact
308
+ saved_artifacts[artifact] = json_artifact
283
309
 
284
310
  return DbtTaskResult(success, results, saved_artifacts)
285
311
 
@@ -373,7 +399,7 @@ class DbtHook(BaseHook):
373
399
  if (project_dir_path / "profiles.yml").exists():
374
400
  # We may have downloaded the profiles.yml file together
375
401
  # with the project.
376
- return (new_project_dir, new_project_dir)
402
+ return new_project_dir, new_project_dir
377
403
 
378
404
  if profiles_dir is not None:
379
405
  profiles_file_path = self.download_dbt_profiles(
@@ -384,7 +410,7 @@ class DbtHook(BaseHook):
384
410
  else:
385
411
  new_profiles_dir = None
386
412
 
387
- return (new_project_dir, new_profiles_dir)
413
+ return new_project_dir, new_profiles_dir
388
414
 
389
415
  def setup_dbt_logging(self, task: BaseTask, debug: Optional[bool]):
390
416
  """Setup dbt logging.
@@ -399,10 +425,18 @@ class DbtHook(BaseHook):
399
425
  if task.config is not None:
400
426
  log_path = getattr(task.config, "log_path", None)
401
427
 
402
- setup_event_logger(log_path or "logs")
428
+ if DBT_INSTALLED_LESS_THAN_1_5:
429
+ setup_event_logger(log_path or "logs")
430
+ else:
431
+ from dbt.flags import get_flags
432
+
433
+ flags = get_flags()
434
+ setup_event_logger(flags)
403
435
 
404
436
  configured_file = logging.getLogger("configured_file")
405
437
  file_log = logging.getLogger("file_log")
438
+ stdout_log = logging.getLogger("stdout_log")
439
+ stdout_log.propagate = True
406
440
 
407
441
  if not debug:
408
442
  # We have to do this after setting logs up as dbt hasn't
@@ -413,16 +447,18 @@ class DbtHook(BaseHook):
413
447
  configured_file.setLevel("INFO")
414
448
  configured_file.propagate = False
415
449
 
416
- def ensure_profiles(self, profiles_dir: Optional[str]):
450
+ def ensure_profiles(self, config: BaseConfig):
417
451
  """Ensure a profiles file exists."""
418
- if profiles_dir is not None:
419
- # We expect one to exist given that we have passsed a profiles_dir.
452
+ if config.profiles_dir is not None:
453
+ # We expect one to exist given that we have passed a profiles_dir.
420
454
  return
421
455
 
422
456
  profiles_path = Path.home() / ".dbt/profiles.yml"
457
+ config.profiles_dir = str(profiles_path.parent)
423
458
  if not profiles_path.exists():
424
459
  profiles_path.parent.mkdir(exist_ok=True)
425
- profiles_path.touch()
460
+ with profiles_path.open("w", encoding="utf-8") as f:
461
+ f.write("config:\n send_anonymous_usage_stats: false\n")
426
462
 
427
463
  def get_dbt_target_from_connection(
428
464
  self, target: Optional[str]
@@ -187,20 +187,26 @@ class DbtGitRemoteHook(SSHHook, DbtRemoteHook):
187
187
  path = f"{url.netloc.split(':')[1]}/{str(url.path)}"
188
188
 
189
189
  elif url.scheme in ("http", "https"):
190
- parsedurl = url._parsed
191
- netloc = parsedurl.hostname
190
+ base_url = url.hostname
192
191
 
193
- if parsedurl.port:
194
- netloc = "{}:{}".format(netloc, parsedurl.port)
195
- if parsedurl.username:
196
- netloc = "{}@{}".format(parsedurl.username, netloc)
192
+ if url.port:
193
+ base_url = f"{base_url}:{url.port}"
197
194
 
198
- url._replace(netloc=netloc)
195
+ auth_params = {}
196
+ if url.authentication.username and url.authentication.password:
197
+ auth_params = {
198
+ "username": url.authentication.username,
199
+ "password": url.authentication.password,
200
+ }
201
+ base_url = f"{url.scheme}://{base_url}"
202
+ elif url.authentication.username:
203
+ base_url = f"{url.scheme}://{url.authentication.username}@{base_url}"
204
+
205
+ client = HttpGitClient(base_url, **auth_params)
199
206
 
200
- client = HttpGitClient(url.netloc)
201
207
  path = str(url.path)
202
208
 
203
209
  else:
204
210
  raise ValueError(f"Unsupported scheme: {url.scheme}")
205
211
 
206
- return (client, path)
212
+ return client, path
@@ -70,6 +70,9 @@ class DbtBaseOperator(BaseOperator):
70
70
  warn_error: Optional[bool] = None,
71
71
  # Logging
72
72
  debug: Optional[bool] = None,
73
+ log_path: Optional[str] = None,
74
+ log_level: Optional[str] = None,
75
+ log_level_file: Optional[str] = None,
73
76
  log_format: Optional[LogFormat] = None,
74
77
  log_cache_events: Optional[bool] = False,
75
78
  quiet: Optional[bool] = None,
@@ -80,12 +83,16 @@ class DbtBaseOperator(BaseOperator):
80
83
  no_defer: Optional[bool] = None,
81
84
  partial_parse: Optional[bool] = False,
82
85
  no_partial_parse: Optional[bool] = None,
86
+ introspect: Optional[bool] = None,
87
+ no_introspect: Optional[bool] = None,
83
88
  use_colors: Optional[bool] = None,
84
89
  no_use_colors: Optional[bool] = None,
85
90
  static_parser: Optional[bool] = None,
86
91
  no_static_parser: Optional[bool] = None,
87
92
  version_check: Optional[bool] = None,
88
93
  no_version_check: Optional[bool] = None,
94
+ write_json: Optional[bool] = None,
95
+ write_perf_info: Optional[bool] = None,
89
96
  anonymous_usage_stats: Optional[bool] = None,
90
97
  no_anonymous_usage_stats: Optional[bool] = None,
91
98
  # Extra features configuration
@@ -116,9 +123,12 @@ class DbtBaseOperator(BaseOperator):
116
123
  self.warn_error = warn_error
117
124
 
118
125
  self.debug = debug
126
+ self.log_path = log_path
119
127
  self.log_cache_events = log_cache_events
120
128
  self.quiet = quiet
121
129
  self.no_print = no_print
130
+ self.log_level = log_level
131
+ self.log_level_file = log_level_file
122
132
  self.log_format = log_format
123
133
  self.record_timing_info = record_timing_info
124
134
 
@@ -137,9 +147,18 @@ class DbtBaseOperator(BaseOperator):
137
147
  self.use_colors = use_colors
138
148
  self.no_use_colors = no_use_colors
139
149
 
140
- self.version_check = no_version_check
150
+ self.introspect = introspect
151
+ self.no_introspect = no_introspect
152
+
153
+ self.version_check = version_check
141
154
  self.no_version_check = no_version_check
142
155
 
156
+ self.write_json = write_json or (
157
+ do_xcom_push_artifacts and "run_results.json" in do_xcom_push_artifacts
158
+ )
159
+
160
+ self.write_perf_info = write_perf_info
161
+
143
162
  self.dbt_conn_id = dbt_conn_id
144
163
  self.profiles_conn_id = profiles_conn_id
145
164
  self.project_conn_id = project_conn_id
@@ -174,7 +193,7 @@ class DbtBaseOperator(BaseOperator):
174
193
  )
175
194
  except Exception as e:
176
195
  self.log.exception(
177
- f"An error has ocurred while executing dbt {self.command}", exc_info=e
196
+ f"An error has occurred while executing dbt {self.command}", exc_info=e
178
197
  )
179
198
  raise AirflowException(
180
199
  f"An error has occurred while executing dbt {self.command}"
@@ -428,11 +447,13 @@ class DbtCleanOperator(DbtBaseOperator):
428
447
  self,
429
448
  upload_dbt_project: bool = True,
430
449
  delete_before_upload: bool = True,
450
+ clean_project_files_only: bool = True,
431
451
  **kwargs,
432
452
  ) -> None:
433
453
  super().__init__(**kwargs)
434
454
  self.upload_dbt_project = upload_dbt_project
435
455
  self.delete_before_upload = delete_before_upload
456
+ self.clean_project_files_only = clean_project_files_only
436
457
 
437
458
  @property
438
459
  def command(self) -> str: