apache-airflow-providers-google 18.0.0rc1__py3-none-any.whl → 18.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apache-airflow-providers-google might be problematic. Click here for more details.

Files changed (72) hide show
  1. airflow/providers/google/__init__.py +1 -1
  2. airflow/providers/google/ads/hooks/ads.py +5 -5
  3. airflow/providers/google/assets/gcs.py +1 -11
  4. airflow/providers/google/cloud/bundles/__init__.py +16 -0
  5. airflow/providers/google/cloud/bundles/gcs.py +161 -0
  6. airflow/providers/google/cloud/hooks/bigquery.py +45 -42
  7. airflow/providers/google/cloud/hooks/cloud_composer.py +131 -1
  8. airflow/providers/google/cloud/hooks/cloud_sql.py +88 -13
  9. airflow/providers/google/cloud/hooks/cloud_storage_transfer_service.py +16 -0
  10. airflow/providers/google/cloud/hooks/dataflow.py +1 -1
  11. airflow/providers/google/cloud/hooks/dataprep.py +1 -1
  12. airflow/providers/google/cloud/hooks/dataproc.py +3 -0
  13. airflow/providers/google/cloud/hooks/gcs.py +107 -3
  14. airflow/providers/google/cloud/hooks/gen_ai.py +196 -0
  15. airflow/providers/google/cloud/hooks/looker.py +1 -1
  16. airflow/providers/google/cloud/hooks/spanner.py +45 -0
  17. airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py +30 -0
  18. airflow/providers/google/cloud/links/base.py +11 -11
  19. airflow/providers/google/cloud/links/dataproc.py +2 -10
  20. airflow/providers/google/cloud/openlineage/CloudStorageTransferJobFacet.json +68 -0
  21. airflow/providers/google/cloud/openlineage/CloudStorageTransferRunFacet.json +60 -0
  22. airflow/providers/google/cloud/openlineage/DataFusionRunFacet.json +32 -0
  23. airflow/providers/google/cloud/openlineage/facets.py +102 -1
  24. airflow/providers/google/cloud/openlineage/mixins.py +3 -1
  25. airflow/providers/google/cloud/operators/bigquery.py +2 -9
  26. airflow/providers/google/cloud/operators/cloud_run.py +2 -1
  27. airflow/providers/google/cloud/operators/cloud_sql.py +1 -1
  28. airflow/providers/google/cloud/operators/cloud_storage_transfer_service.py +89 -6
  29. airflow/providers/google/cloud/operators/datafusion.py +36 -7
  30. airflow/providers/google/cloud/operators/gen_ai.py +389 -0
  31. airflow/providers/google/cloud/operators/spanner.py +22 -6
  32. airflow/providers/google/cloud/operators/vertex_ai/auto_ml.py +7 -0
  33. airflow/providers/google/cloud/operators/vertex_ai/generative_model.py +30 -0
  34. airflow/providers/google/cloud/operators/workflows.py +17 -6
  35. airflow/providers/google/cloud/sensors/bigquery.py +1 -1
  36. airflow/providers/google/cloud/sensors/bigquery_dts.py +1 -6
  37. airflow/providers/google/cloud/sensors/bigtable.py +1 -6
  38. airflow/providers/google/cloud/sensors/cloud_composer.py +65 -31
  39. airflow/providers/google/cloud/sensors/cloud_storage_transfer_service.py +1 -6
  40. airflow/providers/google/cloud/sensors/dataflow.py +1 -1
  41. airflow/providers/google/cloud/sensors/dataform.py +1 -6
  42. airflow/providers/google/cloud/sensors/datafusion.py +1 -6
  43. airflow/providers/google/cloud/sensors/dataplex.py +1 -6
  44. airflow/providers/google/cloud/sensors/dataprep.py +1 -6
  45. airflow/providers/google/cloud/sensors/dataproc.py +1 -6
  46. airflow/providers/google/cloud/sensors/dataproc_metastore.py +1 -6
  47. airflow/providers/google/cloud/sensors/gcs.py +1 -7
  48. airflow/providers/google/cloud/sensors/looker.py +1 -6
  49. airflow/providers/google/cloud/sensors/pubsub.py +1 -6
  50. airflow/providers/google/cloud/sensors/tasks.py +1 -6
  51. airflow/providers/google/cloud/sensors/vertex_ai/feature_store.py +1 -6
  52. airflow/providers/google/cloud/sensors/workflows.py +1 -6
  53. airflow/providers/google/cloud/transfers/bigquery_to_gcs.py +2 -1
  54. airflow/providers/google/cloud/transfers/gcs_to_bigquery.py +2 -1
  55. airflow/providers/google/cloud/transfers/sftp_to_gcs.py +11 -2
  56. airflow/providers/google/cloud/triggers/bigquery.py +15 -3
  57. airflow/providers/google/cloud/triggers/cloud_composer.py +51 -21
  58. airflow/providers/google/cloud/triggers/cloud_run.py +1 -1
  59. airflow/providers/google/cloud/triggers/cloud_storage_transfer_service.py +90 -0
  60. airflow/providers/google/cloud/triggers/pubsub.py +14 -18
  61. airflow/providers/google/common/hooks/base_google.py +1 -1
  62. airflow/providers/google/get_provider_info.py +15 -0
  63. airflow/providers/google/leveldb/hooks/leveldb.py +1 -1
  64. airflow/providers/google/marketing_platform/links/analytics_admin.py +2 -8
  65. airflow/providers/google/marketing_platform/sensors/campaign_manager.py +1 -6
  66. airflow/providers/google/marketing_platform/sensors/display_video.py +1 -6
  67. airflow/providers/google/suite/sensors/drive.py +1 -6
  68. airflow/providers/google/version_compat.py +0 -20
  69. {apache_airflow_providers_google-18.0.0rc1.dist-info → apache_airflow_providers_google-18.1.0.dist-info}/METADATA +15 -15
  70. {apache_airflow_providers_google-18.0.0rc1.dist-info → apache_airflow_providers_google-18.1.0.dist-info}/RECORD +72 -65
  71. {apache_airflow_providers_google-18.0.0rc1.dist-info → apache_airflow_providers_google-18.1.0.dist-info}/WHEEL +0 -0
  72. {apache_airflow_providers_google-18.0.0rc1.dist-info → apache_airflow_providers_google-18.1.0.dist-info}/entry_points.txt +0 -0
@@ -20,9 +20,10 @@ import datetime
20
20
  import json
21
21
  import re
22
22
  import uuid
23
- from collections.abc import Sequence
23
+ from collections.abc import Collection, Sequence
24
24
  from typing import TYPE_CHECKING
25
25
 
26
+ import pendulum
26
27
  from google.api_core.exceptions import AlreadyExists
27
28
  from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
28
29
  from google.cloud.workflows.executions_v1beta import Execution
@@ -36,12 +37,13 @@ from airflow.providers.google.cloud.links.workflows import (
36
37
  )
37
38
  from airflow.providers.google.cloud.operators.cloud_base import GoogleCloudBaseOperator
38
39
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
40
+ from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
39
41
 
40
42
  if TYPE_CHECKING:
41
43
  from google.api_core.retry import Retry
42
44
  from google.protobuf.field_mask_pb2 import FieldMask
43
45
 
44
- from airflow.utils.context import Context
46
+ from airflow.sdk import Context
45
47
 
46
48
  from airflow.utils.hashlib_wrapper import md5
47
49
 
@@ -69,7 +71,7 @@ class WorkflowsCreateWorkflowOperator(GoogleCloudBaseOperator):
69
71
  :param metadata: Additional metadata that is provided to the method.
70
72
  """
71
73
 
72
- template_fields: Sequence[str] = ("location", "workflow", "workflow_id")
74
+ template_fields: Collection[str] = ("location", "workflow", "workflow_id")
73
75
  template_fields_renderers = {"workflow": "json"}
74
76
  operator_extra_links = (WorkflowsWorkflowDetailsLink(),)
75
77
 
@@ -101,7 +103,7 @@ class WorkflowsCreateWorkflowOperator(GoogleCloudBaseOperator):
101
103
  self.impersonation_chain = impersonation_chain
102
104
  self.force_rerun = force_rerun
103
105
 
104
- def _workflow_id(self, context):
106
+ def _workflow_id(self, context: Context) -> str:
105
107
  if self.workflow_id and not self.force_rerun:
106
108
  # If users provide workflow id then assuring the idempotency
107
109
  # is on their side
@@ -114,8 +116,17 @@ class WorkflowsCreateWorkflowOperator(GoogleCloudBaseOperator):
114
116
 
115
117
  # We are limited by allowed length of workflow_id so
116
118
  # we use hash of whole information
117
- exec_date = context["logical_date"].isoformat()
118
- base = f"airflow_{self.dag_id}_{self.task_id}_{exec_date}_{hash_base}"
119
+ if AIRFLOW_V_3_0_PLUS:
120
+ if dag_run := context.get("dag_run"):
121
+ run_after = pendulum.instance(dag_run.run_after)
122
+ else:
123
+ run_after = pendulum.now("UTC")
124
+ else:
125
+ if logical_date := context.get("logical_date"):
126
+ run_after = pendulum.instance(logical_date)
127
+ else:
128
+ run_after = pendulum.now("UTC")
129
+ base = f"airflow_{self.dag_id}_{self.task_id}_{run_after.isoformat()}_{hash_base}"
119
130
  workflow_id = md5(base.encode()).hexdigest()
120
131
  return re.sub(r"[:\-+.]", "_", workflow_id)
121
132
 
@@ -26,12 +26,12 @@ from typing import TYPE_CHECKING, Any
26
26
 
27
27
  from airflow.configuration import conf
28
28
  from airflow.exceptions import AirflowException, AirflowProviderDeprecationWarning
29
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
29
30
  from airflow.providers.google.cloud.hooks.bigquery import BigQueryHook
30
31
  from airflow.providers.google.cloud.triggers.bigquery import (
31
32
  BigQueryTableExistenceTrigger,
32
33
  BigQueryTablePartitionExistenceTrigger,
33
34
  )
34
- from airflow.providers.google.version_compat import BaseSensorOperator
35
35
 
36
36
  if TYPE_CHECKING:
37
37
  from airflow.utils.context import Context
@@ -26,14 +26,9 @@ from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
26
26
  from google.cloud.bigquery_datatransfer_v1 import TransferState
27
27
 
28
28
  from airflow.exceptions import AirflowException
29
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
29
30
  from airflow.providers.google.cloud.hooks.bigquery_dts import BiqQueryDataTransferServiceHook
30
31
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
31
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
32
-
33
- if AIRFLOW_V_3_0_PLUS:
34
- from airflow.sdk import BaseSensorOperator
35
- else:
36
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
37
32
 
38
33
  if TYPE_CHECKING:
39
34
  from google.api_core.retry import Retry
@@ -26,16 +26,11 @@ import google.api_core.exceptions
26
26
  from google.cloud.bigtable import enums
27
27
  from google.cloud.bigtable.table import ClusterState
28
28
 
29
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
29
30
  from airflow.providers.google.cloud.hooks.bigtable import BigtableHook
30
31
  from airflow.providers.google.cloud.links.bigtable import BigtableTablesLink
31
32
  from airflow.providers.google.cloud.operators.bigtable import BigtableValidationMixin
32
33
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
33
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
34
-
35
- if AIRFLOW_V_3_0_PLUS:
36
- from airflow.sdk import BaseSensorOperator
37
- else:
38
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
39
34
 
40
35
  if TYPE_CHECKING:
41
36
  from airflow.utils.context import Context
@@ -26,19 +26,15 @@ from functools import cached_property
26
26
  from typing import TYPE_CHECKING
27
27
 
28
28
  from dateutil import parser
29
+ from google.api_core.exceptions import NotFound
29
30
  from google.cloud.orchestration.airflow.service_v1.types import Environment, ExecuteAirflowCommandResponse
30
31
 
31
32
  from airflow.configuration import conf
32
33
  from airflow.exceptions import AirflowException
34
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
33
35
  from airflow.providers.google.cloud.hooks.cloud_composer import CloudComposerHook
34
36
  from airflow.providers.google.cloud.triggers.cloud_composer import CloudComposerDAGRunTrigger
35
37
  from airflow.providers.google.common.consts import GOOGLE_DEFAULT_DEFERRABLE_METHOD_NAME
36
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
37
-
38
- if AIRFLOW_V_3_0_PLUS:
39
- from airflow.sdk import BaseSensorOperator
40
- else:
41
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
42
38
  from airflow.utils.state import TaskInstanceState
43
39
 
44
40
  if TYPE_CHECKING:
@@ -97,6 +93,7 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
97
93
  impersonation_chain: str | Sequence[str] | None = None,
98
94
  deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False),
99
95
  poll_interval: int = 10,
96
+ use_rest_api: bool = False,
100
97
  **kwargs,
101
98
  ) -> None:
102
99
  super().__init__(**kwargs)
@@ -111,6 +108,7 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
111
108
  self.impersonation_chain = impersonation_chain
112
109
  self.deferrable = deferrable
113
110
  self.poll_interval = poll_interval
111
+ self.use_rest_api = use_rest_api
114
112
 
115
113
  if self.composer_dag_run_id and self.execution_range:
116
114
  self.log.warning(
@@ -118,15 +116,22 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
118
116
  )
119
117
 
120
118
  def _get_logical_dates(self, context) -> tuple[datetime, datetime]:
119
+ logical_date = context.get("logical_date", None)
120
+ if logical_date is None:
121
+ raise RuntimeError(
122
+ "logical_date is None. Please make sure the sensor is not used in an asset-triggered Dag. "
123
+ "CloudComposerDAGRunSensor was designed to be used in time-based scheduled Dags only, "
124
+ "and asset-triggered Dags do not have logical_date. "
125
+ )
121
126
  if isinstance(self.execution_range, timedelta):
122
127
  if self.execution_range < timedelta(0):
123
- return context["logical_date"], context["logical_date"] - self.execution_range
124
- return context["logical_date"] - self.execution_range, context["logical_date"]
128
+ return logical_date, logical_date - self.execution_range
129
+ return logical_date - self.execution_range, logical_date
125
130
  if isinstance(self.execution_range, list) and len(self.execution_range) > 0:
126
131
  return self.execution_range[0], self.execution_range[1] if len(
127
132
  self.execution_range
128
- ) > 1 else context["logical_date"]
129
- return context["logical_date"] - timedelta(1), context["logical_date"]
133
+ ) > 1 else logical_date
134
+ return logical_date - timedelta(1), logical_date
130
135
 
131
136
  def poke(self, context: Context) -> bool:
132
137
  start_date, end_date = self._get_logical_dates(context)
@@ -161,26 +166,51 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
161
166
 
162
167
  def _pull_dag_runs(self) -> list[dict]:
163
168
  """Pull the list of dag runs."""
164
- cmd_parameters = (
165
- ["-d", self.composer_dag_id, "-o", "json"]
166
- if self._composer_airflow_version < 3
167
- else [self.composer_dag_id, "-o", "json"]
168
- )
169
- dag_runs_cmd = self.hook.execute_airflow_command(
170
- project_id=self.project_id,
171
- region=self.region,
172
- environment_id=self.environment_id,
173
- command="dags",
174
- subcommand="list-runs",
175
- parameters=cmd_parameters,
176
- )
177
- cmd_result = self.hook.wait_command_execution_result(
178
- project_id=self.project_id,
179
- region=self.region,
180
- environment_id=self.environment_id,
181
- execution_cmd_info=ExecuteAirflowCommandResponse.to_dict(dag_runs_cmd),
182
- )
183
- dag_runs = json.loads(cmd_result["output"][0]["content"])
169
+ if self.use_rest_api:
170
+ try:
171
+ environment = self.hook.get_environment(
172
+ project_id=self.project_id,
173
+ region=self.region,
174
+ environment_id=self.environment_id,
175
+ timeout=self.timeout,
176
+ )
177
+ except NotFound as not_found_err:
178
+ self.log.info("The Composer environment %s does not exist.", self.environment_id)
179
+ raise AirflowException(not_found_err)
180
+ composer_airflow_uri = environment.config.airflow_uri
181
+
182
+ self.log.info(
183
+ "Pulling the DAG %s runs from the %s environment...",
184
+ self.composer_dag_id,
185
+ self.environment_id,
186
+ )
187
+ dag_runs_response = self.hook.get_dag_runs(
188
+ composer_airflow_uri=composer_airflow_uri,
189
+ composer_dag_id=self.composer_dag_id,
190
+ timeout=self.timeout,
191
+ )
192
+ dag_runs = dag_runs_response["dag_runs"]
193
+ else:
194
+ cmd_parameters = (
195
+ ["-d", self.composer_dag_id, "-o", "json"]
196
+ if self._composer_airflow_version < 3
197
+ else [self.composer_dag_id, "-o", "json"]
198
+ )
199
+ dag_runs_cmd = self.hook.execute_airflow_command(
200
+ project_id=self.project_id,
201
+ region=self.region,
202
+ environment_id=self.environment_id,
203
+ command="dags",
204
+ subcommand="list-runs",
205
+ parameters=cmd_parameters,
206
+ )
207
+ cmd_result = self.hook.wait_command_execution_result(
208
+ project_id=self.project_id,
209
+ region=self.region,
210
+ environment_id=self.environment_id,
211
+ execution_cmd_info=ExecuteAirflowCommandResponse.to_dict(dag_runs_cmd),
212
+ )
213
+ dag_runs = json.loads(cmd_result["output"][0]["content"])
184
214
  return dag_runs
185
215
 
186
216
  def _check_dag_runs_states(
@@ -213,7 +243,10 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
213
243
 
214
244
  def _check_composer_dag_run_id_states(self, dag_runs: list[dict]) -> bool:
215
245
  for dag_run in dag_runs:
216
- if dag_run["run_id"] == self.composer_dag_run_id and dag_run["state"] in self.allowed_states:
246
+ if (
247
+ dag_run["dag_run_id" if self.use_rest_api else "run_id"] == self.composer_dag_run_id
248
+ and dag_run["state"] in self.allowed_states
249
+ ):
217
250
  return True
218
251
  return False
219
252
 
@@ -236,6 +269,7 @@ class CloudComposerDAGRunSensor(BaseSensorOperator):
236
269
  impersonation_chain=self.impersonation_chain,
237
270
  poll_interval=self.poll_interval,
238
271
  composer_airflow_version=self._composer_airflow_version,
272
+ use_rest_api=self.use_rest_api,
239
273
  ),
240
274
  method_name=GOOGLE_DEFAULT_DEFERRABLE_METHOD_NAME,
241
275
  )
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Any
24
24
 
25
25
  from airflow.configuration import conf
26
26
  from airflow.exceptions import AirflowException
27
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
27
28
  from airflow.providers.google.cloud.hooks.cloud_storage_transfer_service import (
28
29
  COUNTERS,
29
30
  METADATA,
@@ -35,12 +36,6 @@ from airflow.providers.google.cloud.triggers.cloud_storage_transfer_service impo
35
36
  CloudStorageTransferServiceCheckJobStatusTrigger,
36
37
  )
37
38
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
38
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
39
-
40
- if AIRFLOW_V_3_0_PLUS:
41
- from airflow.sdk import BaseSensorOperator
42
- else:
43
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
44
39
 
45
40
  if TYPE_CHECKING:
46
41
  from airflow.utils.context import Context
@@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, Any
25
25
 
26
26
  from airflow.configuration import conf
27
27
  from airflow.exceptions import AirflowException
28
+ from airflow.providers.common.compat.sdk import BaseSensorOperator, PokeReturnValue
28
29
  from airflow.providers.google.cloud.hooks.dataflow import (
29
30
  DEFAULT_DATAFLOW_LOCATION,
30
31
  DataflowHook,
@@ -37,7 +38,6 @@ from airflow.providers.google.cloud.triggers.dataflow import (
37
38
  DataflowJobStatusTrigger,
38
39
  )
39
40
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
40
- from airflow.providers.google.version_compat import BaseSensorOperator, PokeReturnValue
41
41
 
42
42
  if TYPE_CHECKING:
43
43
  from airflow.utils.context import Context
@@ -23,13 +23,8 @@ from collections.abc import Iterable, Sequence
23
23
  from typing import TYPE_CHECKING
24
24
 
25
25
  from airflow.exceptions import AirflowException
26
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
26
27
  from airflow.providers.google.cloud.hooks.dataform import DataformHook
27
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
28
-
29
- if AIRFLOW_V_3_0_PLUS:
30
- from airflow.sdk import BaseSensorOperator
31
- else:
32
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
33
28
 
34
29
  if TYPE_CHECKING:
35
30
  from airflow.utils.context import Context
@@ -23,14 +23,9 @@ from collections.abc import Iterable, Sequence
23
23
  from typing import TYPE_CHECKING
24
24
 
25
25
  from airflow.exceptions import AirflowException, AirflowNotFoundException
26
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
26
27
  from airflow.providers.google.cloud.hooks.datafusion import DataFusionHook
27
28
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
28
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
29
-
30
- if AIRFLOW_V_3_0_PLUS:
31
- from airflow.sdk import BaseSensorOperator
32
- else:
33
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
34
29
 
35
30
  if TYPE_CHECKING:
36
31
  from airflow.utils.context import Context
@@ -32,17 +32,12 @@ from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
32
32
  from google.cloud.dataplex_v1.types import DataScanJob
33
33
 
34
34
  from airflow.exceptions import AirflowException
35
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
35
36
  from airflow.providers.google.cloud.hooks.dataplex import (
36
37
  AirflowDataQualityScanException,
37
38
  AirflowDataQualityScanResultTimeoutException,
38
39
  DataplexHook,
39
40
  )
40
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
41
-
42
- if AIRFLOW_V_3_0_PLUS:
43
- from airflow.sdk import BaseSensorOperator
44
- else:
45
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
46
41
 
47
42
 
48
43
  class TaskState:
@@ -22,13 +22,8 @@ from __future__ import annotations
22
22
  from collections.abc import Sequence
23
23
  from typing import TYPE_CHECKING
24
24
 
25
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
25
26
  from airflow.providers.google.cloud.hooks.dataprep import GoogleDataprepHook, JobGroupStatuses
26
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
27
-
28
- if AIRFLOW_V_3_0_PLUS:
29
- from airflow.sdk import BaseSensorOperator
30
- else:
31
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
32
27
 
33
28
  if TYPE_CHECKING:
34
29
  from airflow.utils.context import Context
@@ -27,14 +27,9 @@ from google.api_core.exceptions import ServerError
27
27
  from google.cloud.dataproc_v1.types import Batch, JobStatus
28
28
 
29
29
  from airflow.exceptions import AirflowException
30
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
30
31
  from airflow.providers.google.cloud.hooks.dataproc import DataprocHook
31
32
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
32
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
33
-
34
- if AIRFLOW_V_3_0_PLUS:
35
- from airflow.sdk import BaseSensorOperator
36
- else:
37
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
38
33
 
39
34
  if TYPE_CHECKING:
40
35
  from airflow.utils.context import Context
@@ -21,14 +21,9 @@ from collections.abc import Sequence
21
21
  from typing import TYPE_CHECKING
22
22
 
23
23
  from airflow.exceptions import AirflowException
24
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
24
25
  from airflow.providers.google.cloud.hooks.dataproc_metastore import DataprocMetastoreHook
25
26
  from airflow.providers.google.cloud.hooks.gcs import parse_json_from_gcs
26
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
27
-
28
- if AIRFLOW_V_3_0_PLUS:
29
- from airflow.sdk import BaseSensorOperator
30
- else:
31
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
32
27
 
33
28
  if TYPE_CHECKING:
34
29
  from google.api_core.operation import Operation
@@ -29,6 +29,7 @@ from google.cloud.storage.retry import DEFAULT_RETRY
29
29
 
30
30
  from airflow.configuration import conf
31
31
  from airflow.exceptions import AirflowException
32
+ from airflow.providers.common.compat.sdk import BaseSensorOperator, poke_mode_only
32
33
  from airflow.providers.google.cloud.hooks.gcs import GCSHook
33
34
  from airflow.providers.google.cloud.triggers.gcs import (
34
35
  GCSBlobTrigger,
@@ -36,13 +37,6 @@ from airflow.providers.google.cloud.triggers.gcs import (
36
37
  GCSPrefixBlobTrigger,
37
38
  GCSUploadSessionTrigger,
38
39
  )
39
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
40
-
41
- if AIRFLOW_V_3_0_PLUS:
42
- from airflow.sdk import BaseSensorOperator
43
- from airflow.sdk.bases.sensor import poke_mode_only
44
- else:
45
- from airflow.sensors.base import BaseSensorOperator, poke_mode_only # type: ignore[no-redef]
46
40
 
47
41
  if TYPE_CHECKING:
48
42
  from google.api_core.retry import Retry
@@ -22,13 +22,8 @@ from __future__ import annotations
22
22
  from typing import TYPE_CHECKING
23
23
 
24
24
  from airflow.exceptions import AirflowException
25
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
25
26
  from airflow.providers.google.cloud.hooks.looker import JobStatus, LookerHook
26
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
27
-
28
- if AIRFLOW_V_3_0_PLUS:
29
- from airflow.sdk import BaseSensorOperator
30
- else:
31
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
32
27
 
33
28
  if TYPE_CHECKING:
34
29
  from airflow.utils.context import Context
@@ -28,14 +28,9 @@ from google.cloud.pubsub_v1.types import ReceivedMessage
28
28
 
29
29
  from airflow.configuration import conf
30
30
  from airflow.exceptions import AirflowException
31
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
31
32
  from airflow.providers.google.cloud.hooks.pubsub import PubSubHook
32
33
  from airflow.providers.google.cloud.triggers.pubsub import PubsubPullTrigger
33
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
34
-
35
- if AIRFLOW_V_3_0_PLUS:
36
- from airflow.sdk import BaseSensorOperator
37
- else:
38
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
39
34
 
40
35
  if TYPE_CHECKING:
41
36
  from airflow.utils.context import Context
@@ -22,14 +22,9 @@ from __future__ import annotations
22
22
  from collections.abc import Sequence
23
23
  from typing import TYPE_CHECKING
24
24
 
25
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
25
26
  from airflow.providers.google.cloud.hooks.tasks import CloudTasksHook
26
27
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
27
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
28
-
29
- if AIRFLOW_V_3_0_PLUS:
30
- from airflow.sdk import BaseSensorOperator
31
- else:
32
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
33
28
 
34
29
  if TYPE_CHECKING:
35
30
  from airflow.utils.context import Context
@@ -24,13 +24,8 @@ from collections.abc import Sequence
24
24
  from typing import TYPE_CHECKING
25
25
 
26
26
  from airflow.exceptions import AirflowException
27
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
27
28
  from airflow.providers.google.cloud.hooks.vertex_ai.feature_store import FeatureStoreHook
28
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
29
-
30
- if AIRFLOW_V_3_0_PLUS:
31
- from airflow.sdk import BaseSensorOperator
32
- else:
33
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
34
29
 
35
30
  if TYPE_CHECKING:
36
31
  from airflow.utils.context import Context
@@ -23,14 +23,9 @@ from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
23
23
  from google.cloud.workflows.executions_v1beta import Execution
24
24
 
25
25
  from airflow.exceptions import AirflowException
26
+ from airflow.providers.common.compat.sdk import BaseSensorOperator
26
27
  from airflow.providers.google.cloud.hooks.workflows import WorkflowsHook
27
28
  from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID
28
- from airflow.providers.google.version_compat import AIRFLOW_V_3_0_PLUS
29
-
30
- if AIRFLOW_V_3_0_PLUS:
31
- from airflow.sdk import BaseSensorOperator
32
- else:
33
- from airflow.sensors.base import BaseSensorOperator # type: ignore[no-redef]
34
29
 
35
30
  if TYPE_CHECKING:
36
31
  from google.api_core.retry import Retry
@@ -215,8 +215,9 @@ class BigQueryToGCSOperator(BaseOperator):
215
215
  job_id=self.job_id,
216
216
  dag_id=self.dag_id,
217
217
  task_id=self.task_id,
218
- logical_date=context["logical_date"],
218
+ logical_date=None,
219
219
  configuration=configuration,
220
+ run_after=hook.get_run_after_or_logical_date(context),
220
221
  force_rerun=self.force_rerun,
221
222
  )
222
223
 
@@ -346,8 +346,9 @@ class GCSToBigQueryOperator(BaseOperator):
346
346
  job_id=self.job_id,
347
347
  dag_id=self.dag_id,
348
348
  task_id=self.task_id,
349
- logical_date=context["logical_date"],
349
+ logical_date=None,
350
350
  configuration=self.configuration,
351
+ run_after=hook.get_run_after_or_logical_date(context),
351
352
  force_rerun=self.force_rerun,
352
353
  )
353
354
 
@@ -78,6 +78,8 @@ class SFTPToGCSOperator(BaseOperator):
78
78
  then uploads (may require significant disk space).
79
79
  When ``True``, the file streams directly without using local disk.
80
80
  Defaults to ``False``.
81
+ :param fail_on_file_not_exist: If True, operator fails when file does not exist,
82
+ if False, operator will not fail and skips transfer. Default is True.
81
83
  """
82
84
 
83
85
  template_fields: Sequence[str] = (
@@ -101,6 +103,7 @@ class SFTPToGCSOperator(BaseOperator):
101
103
  impersonation_chain: str | Sequence[str] | None = None,
102
104
  sftp_prefetch: bool = True,
103
105
  use_stream: bool = False,
106
+ fail_on_file_not_exist: bool = True,
104
107
  **kwargs,
105
108
  ) -> None:
106
109
  super().__init__(**kwargs)
@@ -116,6 +119,7 @@ class SFTPToGCSOperator(BaseOperator):
116
119
  self.impersonation_chain = impersonation_chain
117
120
  self.sftp_prefetch = sftp_prefetch
118
121
  self.use_stream = use_stream
122
+ self.fail_on_file_not_exist = fail_on_file_not_exist
119
123
 
120
124
  @cached_property
121
125
  def sftp_hook(self):
@@ -156,7 +160,13 @@ class SFTPToGCSOperator(BaseOperator):
156
160
  destination_object = (
157
161
  self.destination_path if self.destination_path else self.source_path.rsplit("/", 1)[1]
158
162
  )
159
- self._copy_single_object(gcs_hook, self.sftp_hook, self.source_path, destination_object)
163
+ try:
164
+ self._copy_single_object(gcs_hook, self.sftp_hook, self.source_path, destination_object)
165
+ except FileNotFoundError as e:
166
+ if self.fail_on_file_not_exist:
167
+ raise e
168
+ self.log.info("File %s not found on SFTP server. Skipping transfer.", self.source_path)
169
+ return
160
170
 
161
171
  def _copy_single_object(
162
172
  self,
@@ -172,7 +182,6 @@ class SFTPToGCSOperator(BaseOperator):
172
182
  self.destination_bucket,
173
183
  destination_object,
174
184
  )
175
-
176
185
  if self.use_stream:
177
186
  dest_bucket = gcs_hook.get_bucket(self.destination_bucket)
178
187
  dest_blob = dest_bucket.blob(destination_object)
@@ -167,6 +167,7 @@ class BigQueryInsertJobTrigger(BaseTrigger):
167
167
  job_id=self.job_id, project_id=self.project_id, location=self.location
168
168
  )
169
169
  if job_status["status"] == "success":
170
+ self.log.info("BigQuery Job succeeded")
170
171
  yield TriggerEvent(
171
172
  {
172
173
  "job_id": self.job_id,
@@ -176,7 +177,13 @@ class BigQueryInsertJobTrigger(BaseTrigger):
176
177
  )
177
178
  return
178
179
  elif job_status["status"] == "error":
179
- yield TriggerEvent(job_status)
180
+ self.log.info("BigQuery Job failed: %s", job_status)
181
+ yield TriggerEvent(
182
+ {
183
+ "status": job_status["status"],
184
+ "message": job_status["message"],
185
+ }
186
+ )
180
187
  return
181
188
  else:
182
189
  self.log.info(
@@ -334,7 +341,12 @@ class BigQueryGetDataTrigger(BigQueryInsertJobTrigger):
334
341
  )
335
342
  return
336
343
  elif job_status["status"] == "error":
337
- yield TriggerEvent(job_status)
344
+ yield TriggerEvent(
345
+ {
346
+ "status": job_status["status"],
347
+ "message": job_status["message"],
348
+ }
349
+ )
338
350
  return
339
351
  else:
340
352
  self.log.info(
@@ -773,7 +785,7 @@ class BigQueryTablePartitionExistenceTrigger(BigQueryTableExistenceTrigger):
773
785
  return
774
786
  job_id = None
775
787
  elif job_status["status"] == "error":
776
- yield TriggerEvent(job_status)
788
+ yield TriggerEvent({"status": job_status["status"]})
777
789
  return
778
790
  self.log.info("Sleeping for %s seconds.", self.poll_interval)
779
791
  await asyncio.sleep(self.poll_interval)