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
@@ -18,12 +18,16 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import asyncio
21
+ import json
21
22
  import time
22
23
  from collections.abc import MutableSequence, Sequence
23
- from typing import TYPE_CHECKING
24
+ from typing import TYPE_CHECKING, Any
25
+ from urllib.parse import urlencode, urljoin
24
26
 
27
+ from aiohttp import ClientSession
25
28
  from google.api_core.client_options import ClientOptions
26
29
  from google.api_core.gapic_v1.method import DEFAULT, _MethodDefault
30
+ from google.auth.transport.requests import AuthorizedSession, Request
27
31
  from google.cloud.orchestration.airflow.service_v1 import (
28
32
  EnvironmentsAsyncClient,
29
33
  EnvironmentsClient,
@@ -33,7 +37,7 @@ from google.cloud.orchestration.airflow.service_v1 import (
33
37
 
34
38
  from airflow.exceptions import AirflowException
35
39
  from airflow.providers.google.common.consts import CLIENT_INFO
36
- from airflow.providers.google.common.hooks.base_google import GoogleBaseHook
40
+ from airflow.providers.google.common.hooks.base_google import GoogleBaseAsyncHook, GoogleBaseHook
37
41
 
38
42
  if TYPE_CHECKING:
39
43
  from google.api_core.operation import Operation
@@ -76,6 +80,34 @@ class CloudComposerHook(GoogleBaseHook, OperationHelper):
76
80
  client_options=self.client_options,
77
81
  )
78
82
 
83
+ def make_composer_airflow_api_request(
84
+ self,
85
+ method: str,
86
+ airflow_uri: str,
87
+ path: str,
88
+ data: Any | None = None,
89
+ timeout: float | None = None,
90
+ ):
91
+ """
92
+ Make a request to Cloud Composer environment's web server.
93
+
94
+ :param method: The request method to use ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE').
95
+ :param airflow_uri: The URI of the Apache Airflow Web UI hosted within this environment.
96
+ :param path: The path to send the request.
97
+ :param data: Dictionary, list of tuples, bytes, or file-like object to send in the body of the request.
98
+ :param timeout: The timeout for this request.
99
+ """
100
+ authed_session = AuthorizedSession(self.get_credentials())
101
+
102
+ resp = authed_session.request(
103
+ method=method,
104
+ url=urljoin(airflow_uri, path),
105
+ data=data,
106
+ headers={"Content-Type": "application/json"},
107
+ timeout=timeout,
108
+ )
109
+ return resp
110
+
79
111
  def get_operation(self, operation_name):
80
112
  return self.get_environment_client().transport.operations_client.get_operation(name=operation_name)
81
113
 
@@ -408,20 +440,160 @@ class CloudComposerHook(GoogleBaseHook, OperationHelper):
408
440
  self.log.info("Waiting for result...")
409
441
  time.sleep(poll_interval)
410
442
 
443
+ def trigger_dag_run(
444
+ self,
445
+ composer_airflow_uri: str,
446
+ composer_dag_id: str,
447
+ composer_dag_conf: dict | None = None,
448
+ timeout: float | None = None,
449
+ ) -> dict:
450
+ """
451
+ Trigger DAG run for provided Apache Airflow Web UI hosted within Composer environment.
452
+
453
+ :param composer_airflow_uri: The URI of the Apache Airflow Web UI hosted within Composer environment.
454
+ :param composer_dag_id: The ID of DAG which will be triggered.
455
+ :param composer_dag_conf: Configuration parameters for the DAG run.
456
+ :param timeout: The timeout for this request.
457
+ """
458
+ response = self.make_composer_airflow_api_request(
459
+ method="POST",
460
+ airflow_uri=composer_airflow_uri,
461
+ path=f"/api/v1/dags/{composer_dag_id}/dagRuns",
462
+ data=json.dumps(
463
+ {
464
+ "conf": composer_dag_conf or {},
465
+ }
466
+ ),
467
+ timeout=timeout,
468
+ )
411
469
 
412
- class CloudComposerAsyncHook(GoogleBaseHook):
470
+ if response.status_code != 200:
471
+ self.log.error(response.text)
472
+ response.raise_for_status()
473
+
474
+ return response.json()
475
+
476
+ def get_dag_runs(
477
+ self,
478
+ composer_airflow_uri: str,
479
+ composer_dag_id: str,
480
+ timeout: float | None = None,
481
+ ) -> dict:
482
+ """
483
+ Get the list of dag runs for provided DAG.
484
+
485
+ :param composer_airflow_uri: The URI of the Apache Airflow Web UI hosted within Composer environment.
486
+ :param composer_dag_id: The ID of DAG.
487
+ :param timeout: The timeout for this request.
488
+ """
489
+ response = self.make_composer_airflow_api_request(
490
+ method="GET",
491
+ airflow_uri=composer_airflow_uri,
492
+ path=f"/api/v1/dags/{composer_dag_id}/dagRuns",
493
+ timeout=timeout,
494
+ )
495
+
496
+ if response.status_code != 200:
497
+ self.log.error(
498
+ "Failed to get DAG runs for dag_id=%s from %s (status=%s): %s",
499
+ composer_dag_id,
500
+ composer_airflow_uri,
501
+ response.status_code,
502
+ response.text,
503
+ )
504
+ response.raise_for_status()
505
+
506
+ return response.json()
507
+
508
+ def get_task_instances(
509
+ self,
510
+ composer_airflow_uri: str,
511
+ composer_dag_id: str,
512
+ query_parameters: dict | None = None,
513
+ timeout: float | None = None,
514
+ ) -> dict:
515
+ """
516
+ Get the list of task instances for provided DAG.
517
+
518
+ :param composer_airflow_uri: The URI of the Apache Airflow Web UI hosted within Composer environment.
519
+ :param composer_dag_id: The ID of DAG.
520
+ :query_parameters: Query parameters for this request.
521
+ :param timeout: The timeout for this request.
522
+ """
523
+ query_string = f"?{urlencode(query_parameters)}" if query_parameters else ""
524
+
525
+ response = self.make_composer_airflow_api_request(
526
+ method="GET",
527
+ airflow_uri=composer_airflow_uri,
528
+ path=f"/api/v1/dags/{composer_dag_id}/dagRuns/~/taskInstances{query_string}",
529
+ timeout=timeout,
530
+ )
531
+
532
+ if response.status_code != 200:
533
+ self.log.error(
534
+ "Failed to get task instances for dag_id=%s from %s (status=%s): %s",
535
+ composer_dag_id,
536
+ composer_airflow_uri,
537
+ response.status_code,
538
+ response.text,
539
+ )
540
+ response.raise_for_status()
541
+
542
+ return response.json()
543
+
544
+
545
+ class CloudComposerAsyncHook(GoogleBaseAsyncHook):
413
546
  """Hook for Google Cloud Composer async APIs."""
414
547
 
548
+ sync_hook_class = CloudComposerHook
549
+
415
550
  client_options = ClientOptions(api_endpoint="composer.googleapis.com:443")
416
551
 
417
- def get_environment_client(self) -> EnvironmentsAsyncClient:
552
+ async def get_environment_client(self) -> EnvironmentsAsyncClient:
418
553
  """Retrieve client library object that allow access Environments service."""
554
+ sync_hook = await self.get_sync_hook()
419
555
  return EnvironmentsAsyncClient(
420
- credentials=self.get_credentials(),
556
+ credentials=sync_hook.get_credentials(),
421
557
  client_info=CLIENT_INFO,
422
558
  client_options=self.client_options,
423
559
  )
424
560
 
561
+ async def make_composer_airflow_api_request(
562
+ self,
563
+ method: str,
564
+ airflow_uri: str,
565
+ path: str,
566
+ data: Any | None = None,
567
+ timeout: float | None = None,
568
+ ):
569
+ """
570
+ Make a request to Cloud Composer environment's web server.
571
+
572
+ :param method: The request method to use ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE').
573
+ :param airflow_uri: The URI of the Apache Airflow Web UI hosted within this environment.
574
+ :param path: The path to send the request.
575
+ :param data: Dictionary, list of tuples, bytes, or file-like object to send in the body of the request.
576
+ :param timeout: The timeout for this request.
577
+ """
578
+ sync_hook = await self.get_sync_hook()
579
+ credentials = sync_hook.get_credentials()
580
+
581
+ if not credentials.valid:
582
+ credentials.refresh(Request())
583
+
584
+ async with ClientSession() as session:
585
+ async with session.request(
586
+ method=method,
587
+ url=urljoin(airflow_uri, path),
588
+ data=data,
589
+ headers={
590
+ "Content-Type": "application/json",
591
+ "Authorization": f"Bearer {credentials.token}",
592
+ },
593
+ timeout=timeout,
594
+ ) as response:
595
+ return await response.json(), response.status
596
+
425
597
  def get_environment_name(self, project_id, region, environment_id):
426
598
  return f"projects/{project_id}/locations/{region}/environments/{environment_id}"
427
599
 
@@ -429,9 +601,8 @@ class CloudComposerAsyncHook(GoogleBaseHook):
429
601
  return f"projects/{project_id}/locations/{region}"
430
602
 
431
603
  async def get_operation(self, operation_name):
432
- return await self.get_environment_client().transport.operations_client.get_operation(
433
- name=operation_name
434
- )
604
+ client = await self.get_environment_client()
605
+ return await client.transport.operations_client.get_operation(name=operation_name)
435
606
 
436
607
  @GoogleBaseHook.fallback_to_default_project_id
437
608
  async def create_environment(
@@ -454,7 +625,7 @@ class CloudComposerAsyncHook(GoogleBaseHook):
454
625
  :param timeout: The timeout for this request.
455
626
  :param metadata: Strings which should be sent along with the request as metadata.
456
627
  """
457
- client = self.get_environment_client()
628
+ client = await self.get_environment_client()
458
629
  return await client.create_environment(
459
630
  request={"parent": self.get_parent(project_id, region), "environment": environment},
460
631
  retry=retry,
@@ -482,7 +653,7 @@ class CloudComposerAsyncHook(GoogleBaseHook):
482
653
  :param timeout: The timeout for this request.
483
654
  :param metadata: Strings which should be sent along with the request as metadata.
484
655
  """
485
- client = self.get_environment_client()
656
+ client = await self.get_environment_client()
486
657
  name = self.get_environment_name(project_id, region, environment_id)
487
658
  return await client.delete_environment(
488
659
  request={"name": name}, retry=retry, timeout=timeout, metadata=metadata
@@ -518,7 +689,7 @@ class CloudComposerAsyncHook(GoogleBaseHook):
518
689
  :param timeout: The timeout for this request.
519
690
  :param metadata: Strings which should be sent along with the request as metadata.
520
691
  """
521
- client = self.get_environment_client()
692
+ client = await self.get_environment_client()
522
693
  name = self.get_environment_name(project_id, region, environment_id)
523
694
 
524
695
  return await client.update_environment(
@@ -528,6 +699,35 @@ class CloudComposerAsyncHook(GoogleBaseHook):
528
699
  metadata=metadata,
529
700
  )
530
701
 
702
+ @GoogleBaseHook.fallback_to_default_project_id
703
+ async def get_environment(
704
+ self,
705
+ project_id: str,
706
+ region: str,
707
+ environment_id: str,
708
+ retry: AsyncRetry | _MethodDefault = DEFAULT,
709
+ timeout: float | None = None,
710
+ metadata: Sequence[tuple[str, str]] = (),
711
+ ) -> Environment:
712
+ """
713
+ Get an existing environment.
714
+
715
+ :param project_id: Required. The ID of the Google Cloud project that the service belongs to.
716
+ :param region: Required. The ID of the Google Cloud region that the service belongs to.
717
+ :param environment_id: Required. The ID of the Google Cloud environment that the service belongs to.
718
+ :param retry: Designation of what errors, if any, should be retried.
719
+ :param timeout: The timeout for this request.
720
+ :param metadata: Strings which should be sent along with the request as metadata.
721
+ """
722
+ client = await self.get_environment_client()
723
+
724
+ return await client.get_environment(
725
+ request={"name": self.get_environment_name(project_id, region, environment_id)},
726
+ retry=retry,
727
+ timeout=timeout,
728
+ metadata=metadata,
729
+ )
730
+
531
731
  @GoogleBaseHook.fallback_to_default_project_id
532
732
  async def execute_airflow_command(
533
733
  self,
@@ -556,7 +756,7 @@ class CloudComposerAsyncHook(GoogleBaseHook):
556
756
  :param timeout: The timeout for this request.
557
757
  :param metadata: Strings which should be sent along with the request as metadata.
558
758
  """
559
- client = self.get_environment_client()
759
+ client = await self.get_environment_client()
560
760
 
561
761
  return await client.execute_airflow_command(
562
762
  request={
@@ -598,7 +798,7 @@ class CloudComposerAsyncHook(GoogleBaseHook):
598
798
  :param timeout: The timeout for this request.
599
799
  :param metadata: Strings which should be sent along with the request as metadata.
600
800
  """
601
- client = self.get_environment_client()
801
+ client = await self.get_environment_client()
602
802
 
603
803
  return await client.poll_airflow_command(
604
804
  request={
@@ -642,9 +842,82 @@ class CloudComposerAsyncHook(GoogleBaseHook):
642
842
  self.log.exception("Exception occurred while polling CMD result")
643
843
  raise AirflowException(ex)
644
844
 
645
- result_dict = PollAirflowCommandResponse.to_dict(result)
845
+ try:
846
+ result_dict = PollAirflowCommandResponse.to_dict(result)
847
+ except Exception as ex:
848
+ self.log.exception("Exception occurred while transforming PollAirflowCommandResponse")
849
+ raise AirflowException(ex)
850
+
646
851
  if result_dict["output_end"]:
647
852
  return result_dict
648
853
 
649
854
  self.log.info("Sleeping for %s seconds.", poll_interval)
650
855
  await asyncio.sleep(poll_interval)
856
+
857
+ async def get_dag_runs(
858
+ self,
859
+ composer_airflow_uri: str,
860
+ composer_dag_id: str,
861
+ timeout: float | None = None,
862
+ ) -> dict:
863
+ """
864
+ Get the list of dag runs for provided DAG.
865
+
866
+ :param composer_airflow_uri: The URI of the Apache Airflow Web UI hosted within Composer environment.
867
+ :param composer_dag_id: The ID of DAG.
868
+ :param timeout: The timeout for this request.
869
+ """
870
+ response_body, response_status_code = await self.make_composer_airflow_api_request(
871
+ method="GET",
872
+ airflow_uri=composer_airflow_uri,
873
+ path=f"/api/v1/dags/{composer_dag_id}/dagRuns",
874
+ timeout=timeout,
875
+ )
876
+
877
+ if response_status_code != 200:
878
+ self.log.error(
879
+ "Failed to get DAG runs for dag_id=%s from %s (status=%s): %s",
880
+ composer_dag_id,
881
+ composer_airflow_uri,
882
+ response_status_code,
883
+ response_body["title"],
884
+ )
885
+ raise AirflowException(response_body["title"])
886
+
887
+ return response_body
888
+
889
+ async def get_task_instances(
890
+ self,
891
+ composer_airflow_uri: str,
892
+ composer_dag_id: str,
893
+ query_parameters: dict | None = None,
894
+ timeout: float | None = None,
895
+ ) -> dict:
896
+ """
897
+ Get the list of task instances for provided DAG.
898
+
899
+ :param composer_airflow_uri: The URI of the Apache Airflow Web UI hosted within Composer environment.
900
+ :param composer_dag_id: The ID of DAG.
901
+ :query_parameters: Query parameters for this request.
902
+ :param timeout: The timeout for this request.
903
+ """
904
+ query_string = f"?{urlencode(query_parameters)}" if query_parameters else ""
905
+
906
+ response_body, response_status_code = await self.make_composer_airflow_api_request(
907
+ method="GET",
908
+ airflow_uri=composer_airflow_uri,
909
+ path=f"/api/v1/dags/{composer_dag_id}/dagRuns/~/taskInstances{query_string}",
910
+ timeout=timeout,
911
+ )
912
+
913
+ if response_status_code != 200:
914
+ self.log.error(
915
+ "Failed to get task instances for dag_id=%s from %s (status=%s): %s",
916
+ composer_dag_id,
917
+ composer_airflow_uri,
918
+ response_status_code,
919
+ response_body["title"],
920
+ )
921
+ raise AirflowException(response_body["title"])
922
+
923
+ return response_body
@@ -0,0 +1,109 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Sequence
21
+ from typing import TYPE_CHECKING
22
+
23
+ from google.cloud.logging_v2.services.config_service_v2 import ConfigServiceV2Client
24
+ from google.cloud.logging_v2.types import (
25
+ CreateSinkRequest,
26
+ DeleteSinkRequest,
27
+ GetSinkRequest,
28
+ ListSinksRequest,
29
+ LogSink,
30
+ UpdateSinkRequest,
31
+ )
32
+
33
+ from airflow.providers.google.common.consts import CLIENT_INFO
34
+ from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
35
+
36
+ if TYPE_CHECKING:
37
+ from google.protobuf.field_mask_pb2 import FieldMask
38
+
39
+
40
+ class CloudLoggingHook(GoogleBaseHook):
41
+ """
42
+ Hook for Google Cloud Logging Log Sinks API.
43
+
44
+ :param gcp_conn_id: The connection ID to use when fetching connection info.
45
+ :param impersonation_chain: Optional service account to impersonate.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ gcp_conn_id: str = "google_cloud_default",
51
+ impersonation_chain: str | Sequence[str] | None = None,
52
+ **kwargs,
53
+ ) -> None:
54
+ super().__init__(gcp_conn_id=gcp_conn_id, impersonation_chain=impersonation_chain, **kwargs)
55
+ self._client: ConfigServiceV2Client | None = None
56
+
57
+ def get_conn(self) -> ConfigServiceV2Client:
58
+ """Return the Google Cloud Logging Config client."""
59
+ if not self._client:
60
+ self._client = ConfigServiceV2Client(credentials=self.get_credentials(), client_info=CLIENT_INFO)
61
+ return self._client
62
+
63
+ def get_parent(self, project_id):
64
+ return f"projects/{project_id}"
65
+
66
+ @GoogleBaseHook.fallback_to_default_project_id
67
+ def create_sink(
68
+ self, sink: LogSink | dict, unique_writer_identity: bool = True, project_id: str = PROVIDE_PROJECT_ID
69
+ ) -> LogSink:
70
+ if isinstance(sink, dict):
71
+ sink = LogSink(**sink)
72
+ request = CreateSinkRequest(
73
+ parent=self.get_parent(project_id), sink=sink, unique_writer_identity=unique_writer_identity
74
+ )
75
+ return self.get_conn().create_sink(request=request)
76
+
77
+ @GoogleBaseHook.fallback_to_default_project_id
78
+ def get_sink(self, sink_name: str, project_id: str = PROVIDE_PROJECT_ID) -> LogSink:
79
+ request = GetSinkRequest(sink_name=f"projects/{project_id}/sinks/{sink_name}")
80
+ return self.get_conn().get_sink(request=request)
81
+
82
+ @GoogleBaseHook.fallback_to_default_project_id
83
+ def list_sinks(self, page_size: int | None = None, project_id: str = PROVIDE_PROJECT_ID) -> list[LogSink]:
84
+ request = ListSinksRequest(parent=self.get_parent(project_id), page_size=page_size)
85
+ return list(self.get_conn().list_sinks(request=request))
86
+
87
+ @GoogleBaseHook.fallback_to_default_project_id
88
+ def delete_sink(self, sink_name: str, project_id: str = PROVIDE_PROJECT_ID) -> None:
89
+ request = DeleteSinkRequest(sink_name=f"projects/{project_id}/sinks/{sink_name}")
90
+ self.get_conn().delete_sink(request=request)
91
+
92
+ @GoogleBaseHook.fallback_to_default_project_id
93
+ def update_sink(
94
+ self,
95
+ sink_name: str,
96
+ sink: LogSink | dict,
97
+ unique_writer_identity: bool,
98
+ update_mask: FieldMask | dict,
99
+ project_id: str = PROVIDE_PROJECT_ID,
100
+ ) -> LogSink:
101
+ if isinstance(sink, dict):
102
+ sink = LogSink(**sink)
103
+ request = UpdateSinkRequest(
104
+ sink_name=f"projects/{project_id}/sinks/{sink_name}",
105
+ sink=sink,
106
+ unique_writer_identity=unique_writer_identity,
107
+ update_mask=update_mask,
108
+ )
109
+ return self.get_conn().update_sink(request=request)
@@ -38,11 +38,15 @@ from google.cloud.run_v2 import (
38
38
  ServicesClient,
39
39
  UpdateJobRequest,
40
40
  )
41
- from google.longrunning import operations_pb2 # type: ignore[attr-defined]
41
+ from google.longrunning import operations_pb2
42
42
 
43
43
  from airflow.exceptions import AirflowException
44
44
  from airflow.providers.google.common.consts import CLIENT_INFO
45
- from airflow.providers.google.common.hooks.base_google import PROVIDE_PROJECT_ID, GoogleBaseHook
45
+ from airflow.providers.google.common.hooks.base_google import (
46
+ PROVIDE_PROJECT_ID,
47
+ GoogleBaseAsyncHook,
48
+ GoogleBaseHook,
49
+ )
46
50
 
47
51
  if TYPE_CHECKING:
48
52
  from google.api_core import operation
@@ -159,7 +163,7 @@ class CloudRunHook(GoogleBaseHook):
159
163
  return list(itertools.islice(jobs, limit))
160
164
 
161
165
 
162
- class CloudRunAsyncHook(GoogleBaseHook):
166
+ class CloudRunAsyncHook(GoogleBaseAsyncHook):
163
167
  """
164
168
  Async hook for the Google Cloud Run service.
165
169
 
@@ -174,6 +178,8 @@ class CloudRunAsyncHook(GoogleBaseHook):
174
178
  account from the list granting this role to the originating account.
175
179
  """
176
180
 
181
+ sync_hook_class = CloudRunHook
182
+
177
183
  def __init__(
178
184
  self,
179
185
  gcp_conn_id: str = "google_cloud_default",
@@ -183,16 +189,16 @@ class CloudRunAsyncHook(GoogleBaseHook):
183
189
  self._client: JobsAsyncClient | None = None
184
190
  super().__init__(gcp_conn_id=gcp_conn_id, impersonation_chain=impersonation_chain, **kwargs)
185
191
 
186
- def get_conn(self):
192
+ async def get_conn(self):
187
193
  if self._client is None:
188
- self._client = JobsAsyncClient(credentials=self.get_credentials(), client_info=CLIENT_INFO)
194
+ sync_hook = await self.get_sync_hook()
195
+ self._client = JobsAsyncClient(credentials=sync_hook.get_credentials(), client_info=CLIENT_INFO)
189
196
 
190
197
  return self._client
191
198
 
192
199
  async def get_operation(self, operation_name: str) -> operations_pb2.Operation:
193
- return await self.get_conn().get_operation(
194
- operations_pb2.GetOperationRequest(name=operation_name), timeout=120
195
- )
200
+ conn = await self.get_conn()
201
+ return await conn.get_operation(operations_pb2.GetOperationRequest(name=operation_name), timeout=120)
196
202
 
197
203
 
198
204
  class CloudRunServiceHook(GoogleBaseHook):
@@ -258,7 +264,7 @@ class CloudRunServiceHook(GoogleBaseHook):
258
264
  return operation.result()
259
265
 
260
266
 
261
- class CloudRunServiceAsyncHook(GoogleBaseHook):
267
+ class CloudRunServiceAsyncHook(GoogleBaseAsyncHook):
262
268
  """
263
269
  Async hook for the Google Cloud Run services.
264
270
 
@@ -273,6 +279,8 @@ class CloudRunServiceAsyncHook(GoogleBaseHook):
273
279
  account from the list granting this role to the originating account.
274
280
  """
275
281
 
282
+ sync_hook_class = CloudRunServiceHook
283
+
276
284
  def __init__(
277
285
  self,
278
286
  gcp_conn_id: str = "google_cloud_default",