apache-airflow-providers-google 15.1.0rc1__py3-none-any.whl → 19.1.0rc1__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.
Files changed (234) hide show
  1. airflow/providers/google/3rd-party-licenses/NOTICE +2 -12
  2. airflow/providers/google/__init__.py +3 -3
  3. airflow/providers/google/ads/hooks/ads.py +39 -5
  4. airflow/providers/google/ads/operators/ads.py +2 -2
  5. airflow/providers/google/ads/transfers/ads_to_gcs.py +2 -2
  6. airflow/providers/google/assets/gcs.py +1 -11
  7. airflow/providers/google/cloud/bundles/__init__.py +16 -0
  8. airflow/providers/google/cloud/bundles/gcs.py +161 -0
  9. airflow/providers/google/cloud/hooks/bigquery.py +166 -281
  10. airflow/providers/google/cloud/hooks/cloud_composer.py +287 -14
  11. airflow/providers/google/cloud/hooks/cloud_logging.py +109 -0
  12. airflow/providers/google/cloud/hooks/cloud_run.py +17 -9
  13. airflow/providers/google/cloud/hooks/cloud_sql.py +101 -22
  14. airflow/providers/google/cloud/hooks/cloud_storage_transfer_service.py +27 -6
  15. airflow/providers/google/cloud/hooks/compute_ssh.py +5 -1
  16. airflow/providers/google/cloud/hooks/datacatalog.py +9 -1
  17. airflow/providers/google/cloud/hooks/dataflow.py +71 -94
  18. airflow/providers/google/cloud/hooks/datafusion.py +1 -1
  19. airflow/providers/google/cloud/hooks/dataplex.py +1 -1
  20. airflow/providers/google/cloud/hooks/dataprep.py +1 -1
  21. airflow/providers/google/cloud/hooks/dataproc.py +72 -71
  22. airflow/providers/google/cloud/hooks/gcs.py +111 -14
  23. airflow/providers/google/cloud/hooks/gen_ai.py +196 -0
  24. airflow/providers/google/cloud/hooks/kubernetes_engine.py +2 -2
  25. airflow/providers/google/cloud/hooks/looker.py +6 -1
  26. airflow/providers/google/cloud/hooks/mlengine.py +3 -2
  27. airflow/providers/google/cloud/hooks/secret_manager.py +102 -10
  28. airflow/providers/google/cloud/hooks/spanner.py +73 -8
  29. airflow/providers/google/cloud/hooks/stackdriver.py +10 -8
  30. airflow/providers/google/cloud/hooks/translate.py +1 -1
  31. airflow/providers/google/cloud/hooks/vertex_ai/auto_ml.py +0 -209
  32. airflow/providers/google/cloud/hooks/vertex_ai/batch_prediction_job.py +2 -2
  33. airflow/providers/google/cloud/hooks/vertex_ai/custom_job.py +27 -1
  34. airflow/providers/google/cloud/hooks/vertex_ai/experiment_service.py +202 -0
  35. airflow/providers/google/cloud/hooks/vertex_ai/feature_store.py +307 -7
  36. airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py +79 -75
  37. airflow/providers/google/cloud/hooks/vertex_ai/ray.py +223 -0
  38. airflow/providers/google/cloud/hooks/vision.py +2 -2
  39. airflow/providers/google/cloud/hooks/workflows.py +1 -1
  40. airflow/providers/google/cloud/links/alloy_db.py +0 -46
  41. airflow/providers/google/cloud/links/base.py +77 -13
  42. airflow/providers/google/cloud/links/bigquery.py +0 -47
  43. airflow/providers/google/cloud/links/bigquery_dts.py +0 -20
  44. airflow/providers/google/cloud/links/bigtable.py +0 -48
  45. airflow/providers/google/cloud/links/cloud_build.py +0 -73
  46. airflow/providers/google/cloud/links/cloud_functions.py +0 -33
  47. airflow/providers/google/cloud/links/cloud_memorystore.py +0 -58
  48. airflow/providers/google/cloud/links/{life_sciences.py → cloud_run.py} +5 -27
  49. airflow/providers/google/cloud/links/cloud_sql.py +0 -33
  50. airflow/providers/google/cloud/links/cloud_storage_transfer.py +17 -44
  51. airflow/providers/google/cloud/links/cloud_tasks.py +7 -26
  52. airflow/providers/google/cloud/links/compute.py +0 -58
  53. airflow/providers/google/cloud/links/data_loss_prevention.py +0 -169
  54. airflow/providers/google/cloud/links/datacatalog.py +23 -54
  55. airflow/providers/google/cloud/links/dataflow.py +0 -34
  56. airflow/providers/google/cloud/links/dataform.py +0 -64
  57. airflow/providers/google/cloud/links/datafusion.py +1 -96
  58. airflow/providers/google/cloud/links/dataplex.py +0 -154
  59. airflow/providers/google/cloud/links/dataprep.py +0 -24
  60. airflow/providers/google/cloud/links/dataproc.py +11 -95
  61. airflow/providers/google/cloud/links/datastore.py +0 -31
  62. airflow/providers/google/cloud/links/kubernetes_engine.py +9 -60
  63. airflow/providers/google/cloud/links/managed_kafka.py +0 -70
  64. airflow/providers/google/cloud/links/mlengine.py +0 -70
  65. airflow/providers/google/cloud/links/pubsub.py +0 -32
  66. airflow/providers/google/cloud/links/spanner.py +0 -33
  67. airflow/providers/google/cloud/links/stackdriver.py +0 -30
  68. airflow/providers/google/cloud/links/translate.py +17 -187
  69. airflow/providers/google/cloud/links/vertex_ai.py +28 -195
  70. airflow/providers/google/cloud/links/workflows.py +0 -52
  71. airflow/providers/google/cloud/log/gcs_task_handler.py +17 -9
  72. airflow/providers/google/cloud/log/stackdriver_task_handler.py +9 -6
  73. airflow/providers/google/cloud/openlineage/CloudStorageTransferJobFacet.json +68 -0
  74. airflow/providers/google/cloud/openlineage/CloudStorageTransferRunFacet.json +60 -0
  75. airflow/providers/google/cloud/openlineage/DataFusionRunFacet.json +32 -0
  76. airflow/providers/google/cloud/openlineage/facets.py +102 -1
  77. airflow/providers/google/cloud/openlineage/mixins.py +10 -8
  78. airflow/providers/google/cloud/openlineage/utils.py +15 -1
  79. airflow/providers/google/cloud/operators/alloy_db.py +70 -55
  80. airflow/providers/google/cloud/operators/bigquery.py +73 -636
  81. airflow/providers/google/cloud/operators/bigquery_dts.py +3 -5
  82. airflow/providers/google/cloud/operators/bigtable.py +36 -7
  83. airflow/providers/google/cloud/operators/cloud_base.py +21 -1
  84. airflow/providers/google/cloud/operators/cloud_batch.py +2 -2
  85. airflow/providers/google/cloud/operators/cloud_build.py +75 -32
  86. airflow/providers/google/cloud/operators/cloud_composer.py +128 -40
  87. airflow/providers/google/cloud/operators/cloud_logging_sink.py +341 -0
  88. airflow/providers/google/cloud/operators/cloud_memorystore.py +69 -43
  89. airflow/providers/google/cloud/operators/cloud_run.py +23 -5
  90. airflow/providers/google/cloud/operators/cloud_sql.py +8 -16
  91. airflow/providers/google/cloud/operators/cloud_storage_transfer_service.py +92 -11
  92. airflow/providers/google/cloud/operators/compute.py +8 -40
  93. airflow/providers/google/cloud/operators/datacatalog.py +157 -21
  94. airflow/providers/google/cloud/operators/dataflow.py +38 -15
  95. airflow/providers/google/cloud/operators/dataform.py +15 -5
  96. airflow/providers/google/cloud/operators/datafusion.py +41 -20
  97. airflow/providers/google/cloud/operators/dataplex.py +193 -109
  98. airflow/providers/google/cloud/operators/dataprep.py +1 -5
  99. airflow/providers/google/cloud/operators/dataproc.py +78 -35
  100. airflow/providers/google/cloud/operators/dataproc_metastore.py +96 -88
  101. airflow/providers/google/cloud/operators/datastore.py +22 -6
  102. airflow/providers/google/cloud/operators/dlp.py +6 -29
  103. airflow/providers/google/cloud/operators/functions.py +16 -7
  104. airflow/providers/google/cloud/operators/gcs.py +10 -8
  105. airflow/providers/google/cloud/operators/gen_ai.py +389 -0
  106. airflow/providers/google/cloud/operators/kubernetes_engine.py +60 -99
  107. airflow/providers/google/cloud/operators/looker.py +1 -1
  108. airflow/providers/google/cloud/operators/managed_kafka.py +107 -52
  109. airflow/providers/google/cloud/operators/natural_language.py +1 -1
  110. airflow/providers/google/cloud/operators/pubsub.py +60 -14
  111. airflow/providers/google/cloud/operators/spanner.py +25 -12
  112. airflow/providers/google/cloud/operators/speech_to_text.py +1 -2
  113. airflow/providers/google/cloud/operators/stackdriver.py +1 -9
  114. airflow/providers/google/cloud/operators/tasks.py +1 -12
  115. airflow/providers/google/cloud/operators/text_to_speech.py +1 -2
  116. airflow/providers/google/cloud/operators/translate.py +40 -16
  117. airflow/providers/google/cloud/operators/translate_speech.py +1 -2
  118. airflow/providers/google/cloud/operators/vertex_ai/auto_ml.py +39 -19
  119. airflow/providers/google/cloud/operators/vertex_ai/batch_prediction_job.py +29 -9
  120. airflow/providers/google/cloud/operators/vertex_ai/custom_job.py +54 -26
  121. airflow/providers/google/cloud/operators/vertex_ai/dataset.py +70 -8
  122. airflow/providers/google/cloud/operators/vertex_ai/endpoint_service.py +43 -9
  123. airflow/providers/google/cloud/operators/vertex_ai/experiment_service.py +435 -0
  124. airflow/providers/google/cloud/operators/vertex_ai/feature_store.py +532 -1
  125. airflow/providers/google/cloud/operators/vertex_ai/generative_model.py +135 -116
  126. airflow/providers/google/cloud/operators/vertex_ai/hyperparameter_tuning_job.py +11 -9
  127. airflow/providers/google/cloud/operators/vertex_ai/model_service.py +57 -11
  128. airflow/providers/google/cloud/operators/vertex_ai/pipeline_job.py +30 -7
  129. airflow/providers/google/cloud/operators/vertex_ai/ray.py +393 -0
  130. airflow/providers/google/cloud/operators/video_intelligence.py +1 -1
  131. airflow/providers/google/cloud/operators/vision.py +2 -2
  132. airflow/providers/google/cloud/operators/workflows.py +18 -15
  133. airflow/providers/google/cloud/sensors/bigquery.py +2 -2
  134. airflow/providers/google/cloud/sensors/bigquery_dts.py +2 -2
  135. airflow/providers/google/cloud/sensors/bigtable.py +11 -4
  136. airflow/providers/google/cloud/sensors/cloud_composer.py +533 -29
  137. airflow/providers/google/cloud/sensors/cloud_storage_transfer_service.py +2 -2
  138. airflow/providers/google/cloud/sensors/dataflow.py +26 -9
  139. airflow/providers/google/cloud/sensors/dataform.py +2 -2
  140. airflow/providers/google/cloud/sensors/datafusion.py +4 -4
  141. airflow/providers/google/cloud/sensors/dataplex.py +2 -2
  142. airflow/providers/google/cloud/sensors/dataprep.py +2 -2
  143. airflow/providers/google/cloud/sensors/dataproc.py +2 -2
  144. airflow/providers/google/cloud/sensors/dataproc_metastore.py +2 -2
  145. airflow/providers/google/cloud/sensors/gcs.py +4 -4
  146. airflow/providers/google/cloud/sensors/looker.py +2 -2
  147. airflow/providers/google/cloud/sensors/pubsub.py +4 -4
  148. airflow/providers/google/cloud/sensors/tasks.py +2 -2
  149. airflow/providers/google/cloud/sensors/vertex_ai/feature_store.py +2 -2
  150. airflow/providers/google/cloud/sensors/workflows.py +2 -2
  151. airflow/providers/google/cloud/transfers/adls_to_gcs.py +1 -1
  152. airflow/providers/google/cloud/transfers/azure_blob_to_gcs.py +2 -2
  153. airflow/providers/google/cloud/transfers/azure_fileshare_to_gcs.py +2 -2
  154. airflow/providers/google/cloud/transfers/bigquery_to_bigquery.py +11 -8
  155. airflow/providers/google/cloud/transfers/bigquery_to_gcs.py +4 -4
  156. airflow/providers/google/cloud/transfers/bigquery_to_mssql.py +7 -3
  157. airflow/providers/google/cloud/transfers/bigquery_to_mysql.py +12 -1
  158. airflow/providers/google/cloud/transfers/bigquery_to_postgres.py +24 -10
  159. airflow/providers/google/cloud/transfers/bigquery_to_sql.py +104 -5
  160. airflow/providers/google/cloud/transfers/calendar_to_gcs.py +1 -1
  161. airflow/providers/google/cloud/transfers/cassandra_to_gcs.py +2 -2
  162. airflow/providers/google/cloud/transfers/facebook_ads_to_gcs.py +3 -3
  163. airflow/providers/google/cloud/transfers/gcs_to_bigquery.py +20 -12
  164. airflow/providers/google/cloud/transfers/gcs_to_gcs.py +2 -2
  165. airflow/providers/google/cloud/transfers/gcs_to_local.py +5 -3
  166. airflow/providers/google/cloud/transfers/gcs_to_sftp.py +10 -4
  167. airflow/providers/google/cloud/transfers/gdrive_to_gcs.py +6 -2
  168. airflow/providers/google/cloud/transfers/gdrive_to_local.py +2 -2
  169. airflow/providers/google/cloud/transfers/http_to_gcs.py +193 -0
  170. airflow/providers/google/cloud/transfers/local_to_gcs.py +2 -2
  171. airflow/providers/google/cloud/transfers/mssql_to_gcs.py +1 -1
  172. airflow/providers/google/cloud/transfers/oracle_to_gcs.py +36 -11
  173. airflow/providers/google/cloud/transfers/postgres_to_gcs.py +42 -9
  174. airflow/providers/google/cloud/transfers/s3_to_gcs.py +12 -6
  175. airflow/providers/google/cloud/transfers/salesforce_to_gcs.py +2 -2
  176. airflow/providers/google/cloud/transfers/sftp_to_gcs.py +13 -4
  177. airflow/providers/google/cloud/transfers/sheets_to_gcs.py +3 -3
  178. airflow/providers/google/cloud/transfers/sql_to_gcs.py +10 -10
  179. airflow/providers/google/cloud/triggers/bigquery.py +75 -34
  180. airflow/providers/google/cloud/triggers/cloud_build.py +1 -1
  181. airflow/providers/google/cloud/triggers/cloud_composer.py +302 -46
  182. airflow/providers/google/cloud/triggers/cloud_run.py +2 -2
  183. airflow/providers/google/cloud/triggers/cloud_storage_transfer_service.py +91 -1
  184. airflow/providers/google/cloud/triggers/dataflow.py +122 -0
  185. airflow/providers/google/cloud/triggers/datafusion.py +1 -1
  186. airflow/providers/google/cloud/triggers/dataplex.py +14 -2
  187. airflow/providers/google/cloud/triggers/dataproc.py +122 -52
  188. airflow/providers/google/cloud/triggers/kubernetes_engine.py +45 -27
  189. airflow/providers/google/cloud/triggers/mlengine.py +1 -1
  190. airflow/providers/google/cloud/triggers/pubsub.py +15 -19
  191. airflow/providers/google/cloud/utils/bigquery_get_data.py +1 -1
  192. airflow/providers/google/cloud/utils/credentials_provider.py +1 -1
  193. airflow/providers/google/cloud/utils/field_validator.py +1 -2
  194. airflow/providers/google/common/auth_backend/google_openid.py +4 -4
  195. airflow/providers/google/common/deprecated.py +2 -1
  196. airflow/providers/google/common/hooks/base_google.py +27 -8
  197. airflow/providers/google/common/links/storage.py +0 -22
  198. airflow/providers/google/common/utils/get_secret.py +31 -0
  199. airflow/providers/google/common/utils/id_token_credentials.py +3 -4
  200. airflow/providers/google/firebase/operators/firestore.py +2 -2
  201. airflow/providers/google/get_provider_info.py +56 -52
  202. airflow/providers/google/go_module_utils.py +35 -3
  203. airflow/providers/google/leveldb/hooks/leveldb.py +26 -1
  204. airflow/providers/google/leveldb/operators/leveldb.py +2 -2
  205. airflow/providers/google/marketing_platform/hooks/display_video.py +3 -109
  206. airflow/providers/google/marketing_platform/links/analytics_admin.py +5 -14
  207. airflow/providers/google/marketing_platform/operators/analytics_admin.py +1 -2
  208. airflow/providers/google/marketing_platform/operators/campaign_manager.py +5 -5
  209. airflow/providers/google/marketing_platform/operators/display_video.py +28 -489
  210. airflow/providers/google/marketing_platform/operators/search_ads.py +2 -2
  211. airflow/providers/google/marketing_platform/sensors/campaign_manager.py +2 -2
  212. airflow/providers/google/marketing_platform/sensors/display_video.py +3 -63
  213. airflow/providers/google/suite/hooks/calendar.py +1 -1
  214. airflow/providers/google/suite/hooks/sheets.py +15 -1
  215. airflow/providers/google/suite/operators/sheets.py +8 -3
  216. airflow/providers/google/suite/sensors/drive.py +2 -2
  217. airflow/providers/google/suite/transfers/gcs_to_gdrive.py +2 -2
  218. airflow/providers/google/suite/transfers/gcs_to_sheets.py +1 -1
  219. airflow/providers/google/suite/transfers/local_to_drive.py +3 -3
  220. airflow/providers/google/suite/transfers/sql_to_sheets.py +5 -4
  221. airflow/providers/google/version_compat.py +15 -1
  222. {apache_airflow_providers_google-15.1.0rc1.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/METADATA +92 -48
  223. apache_airflow_providers_google-19.1.0rc1.dist-info/RECORD +331 -0
  224. apache_airflow_providers_google-19.1.0rc1.dist-info/licenses/NOTICE +5 -0
  225. airflow/providers/google/cloud/hooks/automl.py +0 -673
  226. airflow/providers/google/cloud/hooks/life_sciences.py +0 -159
  227. airflow/providers/google/cloud/links/automl.py +0 -193
  228. airflow/providers/google/cloud/operators/automl.py +0 -1362
  229. airflow/providers/google/cloud/operators/life_sciences.py +0 -119
  230. airflow/providers/google/cloud/operators/mlengine.py +0 -112
  231. apache_airflow_providers_google-15.1.0rc1.dist-info/RECORD +0 -321
  232. {apache_airflow_providers_google-15.1.0rc1.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/WHEEL +0 -0
  233. {apache_airflow_providers_google-15.1.0rc1.dist-info → apache_airflow_providers_google-19.1.0rc1.dist-info}/entry_points.txt +0 -0
  234. {airflow/providers/google → apache_airflow_providers_google-19.1.0rc1.dist-info/licenses}/LICENSE +0 -0
@@ -20,11 +20,12 @@ from __future__ import annotations
20
20
 
21
21
  import asyncio
22
22
  import json
23
- from collections.abc import Sequence
23
+ from collections.abc import Collection, Iterable, Sequence
24
24
  from datetime import datetime
25
25
  from typing import Any
26
26
 
27
27
  from dateutil import parser
28
+ from google.api_core.exceptions import NotFound
28
29
  from google.cloud.orchestration.airflow.service_v1.types import ExecuteAirflowCommandResponse
29
30
 
30
31
  from airflow.exceptions import AirflowException
@@ -52,11 +53,6 @@ class CloudComposerExecutionTrigger(BaseTrigger):
52
53
  self.impersonation_chain = impersonation_chain
53
54
  self.pooling_period_seconds = pooling_period_seconds
54
55
 
55
- self.gcp_hook = CloudComposerAsyncHook(
56
- gcp_conn_id=self.gcp_conn_id,
57
- impersonation_chain=self.impersonation_chain,
58
- )
59
-
60
56
  def serialize(self) -> tuple[str, dict[str, Any]]:
61
57
  return (
62
58
  "airflow.providers.google.cloud.triggers.cloud_composer.CloudComposerExecutionTrigger",
@@ -70,7 +66,14 @@ class CloudComposerExecutionTrigger(BaseTrigger):
70
66
  },
71
67
  )
72
68
 
69
+ def _get_async_hook(self) -> CloudComposerAsyncHook:
70
+ return CloudComposerAsyncHook(
71
+ gcp_conn_id=self.gcp_conn_id,
72
+ impersonation_chain=self.impersonation_chain,
73
+ )
74
+
73
75
  async def run(self):
76
+ self.gcp_hook = self._get_async_hook()
74
77
  while True:
75
78
  operation = await self.gcp_hook.get_operation(operation_name=self.operation_name)
76
79
  if operation.done:
@@ -108,11 +111,6 @@ class CloudComposerAirflowCLICommandTrigger(BaseTrigger):
108
111
  self.impersonation_chain = impersonation_chain
109
112
  self.poll_interval = poll_interval
110
113
 
111
- self.gcp_hook = CloudComposerAsyncHook(
112
- gcp_conn_id=self.gcp_conn_id,
113
- impersonation_chain=self.impersonation_chain,
114
- )
115
-
116
114
  def serialize(self) -> tuple[str, dict[str, Any]]:
117
115
  return (
118
116
  "airflow.providers.google.cloud.triggers.cloud_composer.CloudComposerAirflowCLICommandTrigger",
@@ -127,7 +125,14 @@ class CloudComposerAirflowCLICommandTrigger(BaseTrigger):
127
125
  },
128
126
  )
129
127
 
128
+ def _get_async_hook(self) -> CloudComposerAsyncHook:
129
+ return CloudComposerAsyncHook(
130
+ gcp_conn_id=self.gcp_conn_id,
131
+ impersonation_chain=self.impersonation_chain,
132
+ )
133
+
130
134
  async def run(self):
135
+ self.gcp_hook = self._get_async_hook()
131
136
  try:
132
137
  result = await self.gcp_hook.wait_command_execution_result(
133
138
  project_id=self.project_id,
@@ -145,10 +150,23 @@ class CloudComposerAirflowCLICommandTrigger(BaseTrigger):
145
150
  )
146
151
  return
147
152
 
153
+ exit_code = result.get("exit_info", {}).get("exit_code")
154
+
155
+ if exit_code == 0:
156
+ yield TriggerEvent(
157
+ {
158
+ "status": "success",
159
+ "result": result,
160
+ }
161
+ )
162
+ return
163
+
164
+ error_output = "".join(line["content"] for line in result.get("error", []))
165
+ message = f"Airflow CLI command failed with exit code {exit_code}.\nError output:\n{error_output}"
148
166
  yield TriggerEvent(
149
167
  {
150
- "status": "success",
151
- "result": result,
168
+ "status": "error",
169
+ "message": message,
152
170
  }
153
171
  )
154
172
  return
@@ -166,10 +184,12 @@ class CloudComposerDAGRunTrigger(BaseTrigger):
166
184
  start_date: datetime,
167
185
  end_date: datetime,
168
186
  allowed_states: list[str],
187
+ composer_dag_run_id: str | None = None,
169
188
  gcp_conn_id: str = "google_cloud_default",
170
189
  impersonation_chain: str | Sequence[str] | None = None,
171
190
  poll_interval: int = 10,
172
191
  composer_airflow_version: int = 2,
192
+ use_rest_api: bool = False,
173
193
  ):
174
194
  super().__init__()
175
195
  self.project_id = project_id
@@ -179,15 +199,12 @@ class CloudComposerDAGRunTrigger(BaseTrigger):
179
199
  self.start_date = start_date
180
200
  self.end_date = end_date
181
201
  self.allowed_states = allowed_states
202
+ self.composer_dag_run_id = composer_dag_run_id
182
203
  self.gcp_conn_id = gcp_conn_id
183
204
  self.impersonation_chain = impersonation_chain
184
205
  self.poll_interval = poll_interval
185
206
  self.composer_airflow_version = composer_airflow_version
186
-
187
- self.gcp_hook = CloudComposerAsyncHook(
188
- gcp_conn_id=self.gcp_conn_id,
189
- impersonation_chain=self.impersonation_chain,
190
- )
207
+ self.use_rest_api = use_rest_api
191
208
 
192
209
  def serialize(self) -> tuple[str, dict[str, Any]]:
193
210
  return (
@@ -200,35 +217,60 @@ class CloudComposerDAGRunTrigger(BaseTrigger):
200
217
  "start_date": self.start_date,
201
218
  "end_date": self.end_date,
202
219
  "allowed_states": self.allowed_states,
220
+ "composer_dag_run_id": self.composer_dag_run_id,
203
221
  "gcp_conn_id": self.gcp_conn_id,
204
222
  "impersonation_chain": self.impersonation_chain,
205
223
  "poll_interval": self.poll_interval,
206
224
  "composer_airflow_version": self.composer_airflow_version,
225
+ "use_rest_api": self.use_rest_api,
207
226
  },
208
227
  )
209
228
 
210
229
  async def _pull_dag_runs(self) -> list[dict]:
211
230
  """Pull the list of dag runs."""
212
- cmd_parameters = (
213
- ["-d", self.composer_dag_id, "-o", "json"]
214
- if self.composer_airflow_version < 3
215
- else [self.composer_dag_id, "-o", "json"]
216
- )
217
- dag_runs_cmd = await self.gcp_hook.execute_airflow_command(
218
- project_id=self.project_id,
219
- region=self.region,
220
- environment_id=self.environment_id,
221
- command="dags",
222
- subcommand="list-runs",
223
- parameters=cmd_parameters,
224
- )
225
- cmd_result = await self.gcp_hook.wait_command_execution_result(
226
- project_id=self.project_id,
227
- region=self.region,
228
- environment_id=self.environment_id,
229
- execution_cmd_info=ExecuteAirflowCommandResponse.to_dict(dag_runs_cmd),
230
- )
231
- dag_runs = json.loads(cmd_result["output"][0]["content"])
231
+ if self.use_rest_api:
232
+ try:
233
+ environment = await self.gcp_hook.get_environment(
234
+ project_id=self.project_id,
235
+ region=self.region,
236
+ environment_id=self.environment_id,
237
+ )
238
+ except NotFound as not_found_err:
239
+ self.log.info("The Composer environment %s does not exist.", self.environment_id)
240
+ raise AirflowException(not_found_err)
241
+ composer_airflow_uri = environment.config.airflow_uri
242
+
243
+ self.log.info(
244
+ "Pulling the DAG %s runs from the %s environment...",
245
+ self.composer_dag_id,
246
+ self.environment_id,
247
+ )
248
+ dag_runs_response = await self.gcp_hook.get_dag_runs(
249
+ composer_airflow_uri=composer_airflow_uri,
250
+ composer_dag_id=self.composer_dag_id,
251
+ )
252
+ dag_runs = dag_runs_response["dag_runs"]
253
+ else:
254
+ cmd_parameters = (
255
+ ["-d", self.composer_dag_id, "-o", "json"]
256
+ if self.composer_airflow_version < 3
257
+ else [self.composer_dag_id, "-o", "json"]
258
+ )
259
+ dag_runs_cmd = await self.gcp_hook.execute_airflow_command(
260
+ project_id=self.project_id,
261
+ region=self.region,
262
+ environment_id=self.environment_id,
263
+ command="dags",
264
+ subcommand="list-runs",
265
+ parameters=cmd_parameters,
266
+ )
267
+ cmd_result = await self.gcp_hook.wait_command_execution_result(
268
+ project_id=self.project_id,
269
+ region=self.region,
270
+ environment_id=self.environment_id,
271
+ execution_cmd_info=ExecuteAirflowCommandResponse.to_dict(dag_runs_cmd),
272
+ )
273
+ dag_runs = json.loads(cmd_result["output"][0]["content"])
232
274
  return dag_runs
233
275
 
234
276
  def _check_dag_runs_states(
@@ -248,20 +290,234 @@ class CloudComposerDAGRunTrigger(BaseTrigger):
248
290
  return False
249
291
  return True
250
292
 
293
+ def _get_async_hook(self) -> CloudComposerAsyncHook:
294
+ return CloudComposerAsyncHook(
295
+ gcp_conn_id=self.gcp_conn_id,
296
+ impersonation_chain=self.impersonation_chain,
297
+ )
298
+
299
+ def _check_composer_dag_run_id_states(self, dag_runs: list[dict]) -> bool:
300
+ for dag_run in dag_runs:
301
+ if (
302
+ dag_run["dag_run_id" if self.use_rest_api else "run_id"] == self.composer_dag_run_id
303
+ and dag_run["state"] in self.allowed_states
304
+ ):
305
+ return True
306
+ return False
307
+
251
308
  async def run(self):
309
+ self.gcp_hook: CloudComposerAsyncHook = self._get_async_hook()
252
310
  try:
253
311
  while True:
254
312
  if datetime.now(self.end_date.tzinfo).timestamp() > self.end_date.timestamp():
255
313
  dag_runs = await self._pull_dag_runs()
256
314
 
257
- self.log.info("Sensor waits for allowed states: %s", self.allowed_states)
258
- if self._check_dag_runs_states(
259
- dag_runs=dag_runs,
260
- start_date=self.start_date,
261
- end_date=self.end_date,
262
- ):
263
- yield TriggerEvent({"status": "success"})
264
- return
315
+ if len(dag_runs) == 0:
316
+ self.log.info("Dag runs are empty. Sensor waits for dag runs...")
317
+ self.log.info("Sleeping for %s seconds.", self.poll_interval)
318
+ await asyncio.sleep(self.poll_interval)
319
+ continue
320
+
321
+ if self.composer_dag_run_id:
322
+ self.log.info(
323
+ "Sensor waits for allowed states %s for specified RunID: %s",
324
+ self.allowed_states,
325
+ self.composer_dag_run_id,
326
+ )
327
+ if self._check_composer_dag_run_id_states(dag_runs=dag_runs):
328
+ yield TriggerEvent({"status": "success"})
329
+ return
330
+ else:
331
+ self.log.info("Sensor waits for allowed states: %s", self.allowed_states)
332
+ if self._check_dag_runs_states(
333
+ dag_runs=dag_runs,
334
+ start_date=self.start_date,
335
+ end_date=self.end_date,
336
+ ):
337
+ yield TriggerEvent({"status": "success"})
338
+ return
339
+ self.log.info("Sleeping for %s seconds.", self.poll_interval)
340
+ await asyncio.sleep(self.poll_interval)
341
+ except AirflowException as ex:
342
+ yield TriggerEvent(
343
+ {
344
+ "status": "error",
345
+ "message": str(ex),
346
+ }
347
+ )
348
+ return
349
+
350
+
351
+ class CloudComposerExternalTaskTrigger(BaseTrigger):
352
+ """The trigger wait for the external task completion."""
353
+
354
+ def __init__(
355
+ self,
356
+ project_id: str,
357
+ region: str,
358
+ environment_id: str,
359
+ start_date: datetime,
360
+ end_date: datetime,
361
+ allowed_states: list[str],
362
+ skipped_states: list[str],
363
+ failed_states: list[str],
364
+ composer_external_dag_id: str,
365
+ composer_external_task_ids: Collection[str] | None = None,
366
+ composer_external_task_group_id: str | None = None,
367
+ gcp_conn_id: str = "google_cloud_default",
368
+ impersonation_chain: str | Sequence[str] | None = None,
369
+ poll_interval: int = 10,
370
+ composer_airflow_version: int = 2,
371
+ ):
372
+ super().__init__()
373
+ self.project_id = project_id
374
+ self.region = region
375
+ self.environment_id = environment_id
376
+ self.start_date = start_date
377
+ self.end_date = end_date
378
+ self.allowed_states = allowed_states
379
+ self.skipped_states = skipped_states
380
+ self.failed_states = failed_states
381
+ self.composer_external_dag_id = composer_external_dag_id
382
+ self.composer_external_task_ids = composer_external_task_ids
383
+ self.composer_external_task_group_id = composer_external_task_group_id
384
+ self.gcp_conn_id = gcp_conn_id
385
+ self.impersonation_chain = impersonation_chain
386
+ self.poll_interval = poll_interval
387
+ self.composer_airflow_version = composer_airflow_version
388
+
389
+ def serialize(self) -> tuple[str, dict[str, Any]]:
390
+ return (
391
+ "airflow.providers.google.cloud.triggers.cloud_composer.CloudComposerExternalTaskTrigger",
392
+ {
393
+ "project_id": self.project_id,
394
+ "region": self.region,
395
+ "environment_id": self.environment_id,
396
+ "start_date": self.start_date,
397
+ "end_date": self.end_date,
398
+ "allowed_states": self.allowed_states,
399
+ "skipped_states": self.skipped_states,
400
+ "failed_states": self.failed_states,
401
+ "composer_external_dag_id": self.composer_external_dag_id,
402
+ "composer_external_task_ids": self.composer_external_task_ids,
403
+ "composer_external_task_group_id": self.composer_external_task_group_id,
404
+ "gcp_conn_id": self.gcp_conn_id,
405
+ "impersonation_chain": self.impersonation_chain,
406
+ "poll_interval": self.poll_interval,
407
+ "composer_airflow_version": self.composer_airflow_version,
408
+ },
409
+ )
410
+
411
+ async def _get_task_instances(self, start_date: str, end_date: str) -> list[dict]:
412
+ """Get the list of task instances."""
413
+ try:
414
+ environment = await self.gcp_hook.get_environment(
415
+ project_id=self.project_id,
416
+ region=self.region,
417
+ environment_id=self.environment_id,
418
+ )
419
+ except NotFound as not_found_err:
420
+ self.log.info("The Composer environment %s does not exist.", self.environment_id)
421
+ raise AirflowException(not_found_err)
422
+ composer_airflow_uri = environment.config.airflow_uri
423
+
424
+ self.log.info(
425
+ "Pulling the DAG '%s' task instances from the '%s' environment...",
426
+ self.composer_external_dag_id,
427
+ self.environment_id,
428
+ )
429
+ task_instances_response = await self.gcp_hook.get_task_instances(
430
+ composer_airflow_uri=composer_airflow_uri,
431
+ composer_dag_id=self.composer_external_dag_id,
432
+ query_parameters={
433
+ "execution_date_gte" if self.composer_airflow_version < 3 else "logical_date_gte": start_date,
434
+ "execution_date_lte" if self.composer_airflow_version < 3 else "logical_date_lte": end_date,
435
+ },
436
+ )
437
+ task_instances = task_instances_response["task_instances"]
438
+
439
+ if self.composer_external_task_ids:
440
+ task_instances = [
441
+ task_instance
442
+ for task_instance in task_instances
443
+ if task_instance["task_id"] in self.composer_external_task_ids
444
+ ]
445
+ elif self.composer_external_task_group_id:
446
+ task_instances = [
447
+ task_instance
448
+ for task_instance in task_instances
449
+ if self.composer_external_task_group_id in task_instance["task_id"].split(".")
450
+ ]
451
+
452
+ return task_instances
453
+
454
+ def _check_task_instances_states(
455
+ self,
456
+ task_instances: list[dict],
457
+ start_date: datetime,
458
+ end_date: datetime,
459
+ states: Iterable[str],
460
+ ) -> bool:
461
+ for task_instance in task_instances:
462
+ if (
463
+ start_date.timestamp()
464
+ < parser.parse(
465
+ task_instance["execution_date" if self.composer_airflow_version < 3 else "logical_date"]
466
+ ).timestamp()
467
+ < end_date.timestamp()
468
+ ) and task_instance["state"] not in states:
469
+ return False
470
+ return True
471
+
472
+ def _get_async_hook(self) -> CloudComposerAsyncHook:
473
+ return CloudComposerAsyncHook(
474
+ gcp_conn_id=self.gcp_conn_id,
475
+ impersonation_chain=self.impersonation_chain,
476
+ )
477
+
478
+ async def run(self):
479
+ self.gcp_hook: CloudComposerAsyncHook = self._get_async_hook()
480
+ try:
481
+ while True:
482
+ task_instances = await self._get_task_instances(
483
+ start_date=self.start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
484
+ end_date=self.end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
485
+ )
486
+
487
+ if len(task_instances) == 0:
488
+ self.log.info("Task Instances are empty. Sensor waits for task instances...")
489
+ self.log.info("Sleeping for %s seconds.", self.poll_interval)
490
+ await asyncio.sleep(self.poll_interval)
491
+ continue
492
+
493
+ if self.failed_states and self._check_task_instances_states(
494
+ task_instances=task_instances,
495
+ start_date=self.start_date,
496
+ end_date=self.end_date,
497
+ states=self.failed_states,
498
+ ):
499
+ yield TriggerEvent({"status": "failed"})
500
+ return
501
+
502
+ if self.skipped_states and self._check_task_instances_states(
503
+ task_instances=task_instances,
504
+ start_date=self.start_date,
505
+ end_date=self.end_date,
506
+ states=self.skipped_states,
507
+ ):
508
+ yield TriggerEvent({"status": "skipped"})
509
+ return
510
+
511
+ self.log.info("Sensor waits for allowed states: %s", self.allowed_states)
512
+ if self._check_task_instances_states(
513
+ task_instances=task_instances,
514
+ start_date=self.start_date,
515
+ end_date=self.end_date,
516
+ states=self.allowed_states,
517
+ ):
518
+ yield TriggerEvent({"status": "success"})
519
+ return
520
+
265
521
  self.log.info("Sleeping for %s seconds.", self.poll_interval)
266
522
  await asyncio.sleep(self.poll_interval)
267
523
  except AirflowException as ex:
@@ -26,7 +26,7 @@ from airflow.providers.google.cloud.hooks.cloud_run import CloudRunAsyncHook
26
26
  from airflow.triggers.base import BaseTrigger, TriggerEvent
27
27
 
28
28
  if TYPE_CHECKING:
29
- from google.longrunning import operations_pb2 # type: ignore[attr-defined]
29
+ from google.longrunning import operations_pb2
30
30
 
31
31
  DEFAULT_BATCH_LOCATION = "us-central1"
32
32
 
@@ -134,7 +134,7 @@ class CloudRunJobFinishedTrigger(BaseTrigger):
134
134
 
135
135
  yield TriggerEvent(
136
136
  {
137
- "status": RunJobStatus.TIMEOUT,
137
+ "status": RunJobStatus.TIMEOUT.value,
138
138
  "job_name": self.job_name,
139
139
  }
140
140
  )
@@ -23,6 +23,7 @@ from typing import Any
23
23
 
24
24
  from google.api_core.exceptions import GoogleAPIError
25
25
  from google.cloud.storage_transfer_v1.types import TransferOperation
26
+ from google.protobuf.json_format import MessageToDict
26
27
 
27
28
  from airflow.exceptions import AirflowException
28
29
  from airflow.providers.google.cloud.hooks.cloud_storage_transfer_service import (
@@ -68,7 +69,7 @@ class CloudStorageTransferServiceCreateJobsTrigger(BaseTrigger):
68
69
  },
69
70
  )
70
71
 
71
- async def run(self) -> AsyncIterator[TriggerEvent]: # type: ignore[override]
72
+ async def run(self) -> AsyncIterator[TriggerEvent]:
72
73
  """Get current data storage transfer jobs and yields a TriggerEvent."""
73
74
  async_hook: CloudDataTransferServiceAsyncHook = self.get_async_hook()
74
75
 
@@ -231,3 +232,92 @@ class CloudStorageTransferServiceCheckJobStatusTrigger(BaseTrigger):
231
232
  except Exception as e:
232
233
  self.log.exception("Exception occurred while checking for query completion")
233
234
  yield TriggerEvent({"status": "error", "message": str(e)})
235
+
236
+
237
+ class CloudDataTransferServiceRunJobTrigger(BaseTrigger):
238
+ """
239
+ CloudDataTransferServiceRunJobTrigger run on the trigger worker to run Cloud Storage Transfer job.
240
+
241
+ :param job_name: The name of the transfer job
242
+ :param project_id: The ID of the project that owns the Transfer Job.
243
+ :param poke_interval: Polling period in seconds to check for the status
244
+ :param gcp_conn_id: The connection ID used to connect to Google Cloud.
245
+ :param impersonation_chain: Optional service account to impersonate using short-term
246
+ credentials, or chained list of accounts required to get the access_token
247
+ of the last account in the list, which will be impersonated in the request.
248
+ If set as a string, the account must grant the originating account
249
+ the Service Account Token Creator IAM role.
250
+ If set as a sequence, the identities from the list must grant
251
+ Service Account Token Creator IAM role to the directly preceding identity, with first
252
+ account from the list granting this role to the originating account (templated).
253
+ """
254
+
255
+ def __init__(
256
+ self,
257
+ job_name: str,
258
+ project_id: str = PROVIDE_PROJECT_ID,
259
+ poke_interval: float = 10.0,
260
+ gcp_conn_id: str = "google_cloud_default",
261
+ impersonation_chain: str | Sequence[str] | None = None,
262
+ ):
263
+ super().__init__()
264
+ self.job_name = job_name
265
+ self.project_id = project_id
266
+ self.poke_interval = poke_interval
267
+ self.gcp_conn_id = gcp_conn_id
268
+ self.impersonation_chain = impersonation_chain
269
+
270
+ def serialize(self) -> tuple[str, dict[str, Any]]:
271
+ """Serialize CloudDataTransferServiceRunJobTrigger arguments and classpath."""
272
+ return (
273
+ f"{self.__class__.__module__}.{self.__class__.__qualname__}",
274
+ {
275
+ "job_name": self.job_name,
276
+ "project_id": self.project_id,
277
+ "poke_interval": self.poke_interval,
278
+ "gcp_conn_id": self.gcp_conn_id,
279
+ "impersonation_chain": self.impersonation_chain,
280
+ },
281
+ )
282
+
283
+ def _get_async_hook(self) -> CloudDataTransferServiceAsyncHook:
284
+ return CloudDataTransferServiceAsyncHook(
285
+ project_id=self.project_id,
286
+ gcp_conn_id=self.gcp_conn_id,
287
+ impersonation_chain=self.impersonation_chain,
288
+ )
289
+
290
+ async def run(self) -> AsyncIterator[TriggerEvent]:
291
+ """Run the transfer job and yield a TriggerEvent."""
292
+ hook = self._get_async_hook()
293
+
294
+ try:
295
+ job_operation = await hook.run_transfer_job(self.job_name)
296
+ while True:
297
+ job_completed = await job_operation.done()
298
+ if job_completed:
299
+ yield TriggerEvent(
300
+ {
301
+ "status": "success",
302
+ "message": "Transfer operation run completed successfully",
303
+ "job_result": {
304
+ "name": job_operation.operation.name,
305
+ "metadata": MessageToDict(
306
+ job_operation.operation.metadata, preserving_proto_field_name=True
307
+ ),
308
+ "response": MessageToDict(
309
+ job_operation.operation.response, preserving_proto_field_name=True
310
+ ),
311
+ },
312
+ }
313
+ )
314
+ return
315
+
316
+ self.log.info(
317
+ "Sleeping for %s seconds.",
318
+ self.poke_interval,
319
+ )
320
+ await asyncio.sleep(self.poke_interval)
321
+ except Exception as e:
322
+ self.log.exception("Exception occurred while running transfer job")
323
+ yield TriggerEvent({"status": "error", "message": str(e)})