apache-airflow-providers-amazon 8.0.0rc1__py3-none-any.whl → 8.0.0rc2__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 (19) hide show
  1. airflow/providers/amazon/aws/hooks/base_aws.py +101 -18
  2. airflow/providers/amazon/aws/hooks/batch_waiters.py +3 -1
  3. airflow/providers/amazon/aws/hooks/emr.py +35 -5
  4. airflow/providers/amazon/aws/links/emr.py +1 -3
  5. airflow/providers/amazon/aws/operators/emr.py +22 -0
  6. airflow/providers/amazon/aws/operators/rds.py +1 -1
  7. airflow/providers/amazon/aws/operators/redshift_cluster.py +22 -1
  8. airflow/providers/amazon/aws/transfers/dynamodb_to_s3.py +1 -1
  9. airflow/providers/amazon/aws/triggers/redshift_cluster.py +54 -2
  10. airflow/providers/amazon/aws/waiters/base_waiter.py +12 -1
  11. airflow/providers/amazon/aws/waiters/emr-serverless.json +18 -0
  12. airflow/providers/amazon/get_provider_info.py +1 -0
  13. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/METADATA +11 -4
  14. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/RECORD +19 -18
  15. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/LICENSE +0 -0
  16. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/NOTICE +0 -0
  17. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/WHEEL +0 -0
  18. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/entry_points.txt +0 -0
  19. {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -53,10 +53,10 @@ from airflow.compat.functools import cached_property
53
53
  from airflow.configuration import conf
54
54
  from airflow.exceptions import (
55
55
  AirflowException,
56
+ AirflowNotFoundException,
56
57
  )
57
58
  from airflow.hooks.base import BaseHook
58
59
  from airflow.providers.amazon.aws.utils.connection_wrapper import AwsConnectionWrapper
59
- from airflow.providers.amazon.aws.waiters.base_waiter import BaseBotoWaiter
60
60
  from airflow.providers_manager import ProvidersManager
61
61
  from airflow.utils.helpers import exactly_one
62
62
  from airflow.utils.log.logging_mixin import LoggingMixin
@@ -70,12 +70,15 @@ if TYPE_CHECKING:
70
70
 
71
71
  class BaseSessionFactory(LoggingMixin):
72
72
  """
73
- Base AWS Session Factory class to handle boto3 session creation.
73
+ Base AWS Session Factory class to handle synchronous and async boto session creation.
74
74
  It can handle most of the AWS supported authentication methods.
75
75
 
76
76
  User can also derive from this class to have full control of boto3 session
77
77
  creation or to support custom federation.
78
78
 
79
+ Note: Not all features implemented for synchronous sessions are available for async
80
+ sessions.
81
+
79
82
  .. seealso::
80
83
  - :ref:`howto/connection:aws:session-factory`
81
84
  """
@@ -125,17 +128,50 @@ class BaseSessionFactory(LoggingMixin):
125
128
  """Assume Role ARN from AWS Connection"""
126
129
  return self.conn.role_arn
127
130
 
128
- def create_session(self) -> boto3.session.Session:
129
- """Create boto3 Session from connection config."""
131
+ def _apply_session_kwargs(self, session):
132
+ if self.conn.session_kwargs.get("profile_name", None) is not None:
133
+ session.set_config_variable("profile", self.conn.session_kwargs["profile_name"])
134
+
135
+ if (
136
+ self.conn.session_kwargs.get("aws_access_key_id", None)
137
+ or self.conn.session_kwargs.get("aws_secret_access_key", None)
138
+ or self.conn.session_kwargs.get("aws_session_token", None)
139
+ ):
140
+ session.set_credentials(
141
+ self.conn.session_kwargs["aws_access_key_id"],
142
+ self.conn.session_kwargs["aws_secret_access_key"],
143
+ self.conn.session_kwargs["aws_session_token"],
144
+ )
145
+
146
+ if self.conn.session_kwargs.get("region_name", None) is not None:
147
+ session.set_config_variable("region", self.conn.session_kwargs["region_name"])
148
+
149
+ def get_async_session(self):
150
+ from aiobotocore.session import get_session as async_get_session
151
+
152
+ return async_get_session()
153
+
154
+ def create_session(self, deferrable: bool = False) -> boto3.session.Session:
155
+ """Create boto3 or aiobotocore Session from connection config."""
130
156
  if not self.conn:
131
157
  self.log.info(
132
158
  "No connection ID provided. Fallback on boto3 credential strategy (region_name=%r). "
133
159
  "See: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html",
134
160
  self.region_name,
135
161
  )
136
- return boto3.session.Session(region_name=self.region_name)
162
+ if deferrable:
163
+ session = self.get_async_session()
164
+ self._apply_session_kwargs(session)
165
+ return session
166
+ else:
167
+ return boto3.session.Session(region_name=self.region_name)
137
168
  elif not self.role_arn:
138
- return self.basic_session
169
+ if deferrable:
170
+ session = self.get_async_session()
171
+ self._apply_session_kwargs(session)
172
+ return session
173
+ else:
174
+ return self.basic_session
139
175
 
140
176
  # Values stored in ``AwsConnectionWrapper.session_kwargs`` are intended to be used only
141
177
  # to create the initial boto3 session.
@@ -148,12 +184,18 @@ class BaseSessionFactory(LoggingMixin):
148
184
  assume_session_kwargs = {}
149
185
  if self.conn.region_name:
150
186
  assume_session_kwargs["region_name"] = self.conn.region_name
151
- return self._create_session_with_assume_role(session_kwargs=assume_session_kwargs)
187
+ return self._create_session_with_assume_role(
188
+ session_kwargs=assume_session_kwargs, deferrable=deferrable
189
+ )
152
190
 
153
191
  def _create_basic_session(self, session_kwargs: dict[str, Any]) -> boto3.session.Session:
154
192
  return boto3.session.Session(**session_kwargs)
155
193
 
156
- def _create_session_with_assume_role(self, session_kwargs: dict[str, Any]) -> boto3.session.Session:
194
+ def _create_session_with_assume_role(
195
+ self, session_kwargs: dict[str, Any], deferrable: bool = False
196
+ ) -> boto3.session.Session:
197
+ from aiobotocore.session import get_session as async_get_session
198
+
157
199
  if self.conn.assume_role_method == "assume_role_with_web_identity":
158
200
  # Deferred credentials have no initial credentials
159
201
  credential_fetcher = self._get_web_identity_credential_fetcher()
@@ -170,10 +212,10 @@ class BaseSessionFactory(LoggingMixin):
170
212
  method="sts-assume-role",
171
213
  )
172
214
 
173
- session = botocore.session.get_session()
215
+ session = async_get_session() if deferrable else botocore.session.get_session()
216
+
174
217
  session._credentials = credentials
175
- region_name = self.basic_session.region_name
176
- session.set_config_variable("region", region_name)
218
+ session.set_config_variable("region", self.basic_session.region_name)
177
219
 
178
220
  return boto3.session.Session(botocore_session=session, **session_kwargs)
179
221
 
@@ -498,7 +540,12 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
498
540
  """Get the Airflow Connection object and wrap it in helper (cached)."""
499
541
  connection = None
500
542
  if self.aws_conn_id:
501
- connection = self.get_connection(self.aws_conn_id)
543
+ try:
544
+ connection = self.get_connection(self.aws_conn_id)
545
+ except AirflowNotFoundException:
546
+ self.log.warning(
547
+ "Unable to find AWS Connection ID '%s', switching to empty.", self.aws_conn_id
548
+ )
502
549
 
503
550
  return AwsConnectionWrapper(
504
551
  conn=connection, region_name=self._region_name, botocore_config=self._config, verify=self._verify
@@ -524,11 +571,11 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
524
571
  """Verify or not SSL certificates boto3 client/resource read-only property."""
525
572
  return self.conn_config.verify
526
573
 
527
- def get_session(self, region_name: str | None = None) -> boto3.session.Session:
574
+ def get_session(self, region_name: str | None = None, deferrable: bool = False) -> boto3.session.Session:
528
575
  """Get the underlying boto3.session.Session(region_name=region_name)."""
529
576
  return SessionFactory(
530
577
  conn=self.conn_config, region_name=region_name, config=self.config
531
- ).create_session()
578
+ ).create_session(deferrable=deferrable)
532
579
 
533
580
  def _get_config(self, config: Config | None = None) -> Config:
534
581
  """
@@ -551,10 +598,19 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
551
598
  self,
552
599
  region_name: str | None = None,
553
600
  config: Config | None = None,
601
+ deferrable: bool = False,
554
602
  ) -> boto3.client:
555
603
  """Get the underlying boto3 client using boto3 session"""
556
604
  client_type = self.client_type
557
- session = self.get_session(region_name=region_name)
605
+ session = self.get_session(region_name=region_name, deferrable=deferrable)
606
+ if not isinstance(session, boto3.session.Session):
607
+ return session.create_client(
608
+ client_type,
609
+ endpoint_url=self.conn_config.endpoint_url,
610
+ config=self._get_config(config),
611
+ verify=self.verify,
612
+ )
613
+
558
614
  return session.client(
559
615
  client_type,
560
616
  endpoint_url=self.conn_config.endpoint_url,
@@ -594,6 +650,14 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
594
650
  else:
595
651
  return self.get_resource_type(region_name=self.region_name)
596
652
 
653
+ @property
654
+ def async_conn(self):
655
+ """Get an aiobotocore client to use for async operations."""
656
+ if not self.client_type:
657
+ raise ValueError("client_type must be specified.")
658
+
659
+ return self.get_client_type(region_name=self.region_name, deferrable=True)
660
+
597
661
  @cached_property
598
662
  def conn_client_meta(self) -> ClientMeta:
599
663
  """Get botocore client metadata from Hook connection (cached)."""
@@ -747,18 +811,35 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
747
811
  path = Path(__file__).parents[1].joinpath(f"waiters/{filename}.json").resolve()
748
812
  return path if path.exists() else None
749
813
 
750
- def get_waiter(self, waiter_name: str, parameters: dict[str, str] | None = None) -> Waiter:
814
+ def get_waiter(
815
+ self,
816
+ waiter_name: str,
817
+ parameters: dict[str, str] | None = None,
818
+ deferrable: bool = False,
819
+ client=None,
820
+ ) -> Waiter:
751
821
  """
752
822
  First checks if there is a custom waiter with the provided waiter_name and
753
823
  uses that if it exists, otherwise it will check the service client for a
754
824
  waiter that matches the name and pass that through.
755
825
 
826
+ If `deferrable` is True, the waiter will be an AIOWaiter, generated from the
827
+ client that is passed as a parameter. If `deferrable` is True, `client` must be
828
+ provided.
829
+
756
830
  :param waiter_name: The name of the waiter. The name should exactly match the
757
831
  name of the key in the waiter model file (typically this is CamelCase).
758
832
  :param parameters: will scan the waiter config for the keys of that dict, and replace them with the
759
833
  corresponding value. If a custom waiter has such keys to be expanded, they need to be provided
760
834
  here.
835
+ :param deferrable: If True, the waiter is going to be an async custom waiter.
836
+
761
837
  """
838
+ from airflow.providers.amazon.aws.waiters.base_waiter import BaseBotoWaiter
839
+
840
+ if deferrable and not client:
841
+ raise ValueError("client must be provided for a deferrable waiter.")
842
+ client = client or self.conn
762
843
  if self.waiter_path and (waiter_name in self._list_custom_waiters()):
763
844
  # Technically if waiter_name is in custom_waiters then self.waiter_path must
764
845
  # exist but MyPy doesn't like the fact that self.waiter_path could be None.
@@ -766,7 +847,9 @@ class AwsGenericHook(BaseHook, Generic[BaseAwsConnection]):
766
847
  config = json.loads(config_file.read())
767
848
 
768
849
  config = self._apply_parameters_value(config, waiter_name, parameters)
769
- return BaseBotoWaiter(client=self.conn, model_config=config).waiter(waiter_name)
850
+ return BaseBotoWaiter(client=client, model_config=config, deferrable=deferrable).waiter(
851
+ waiter_name
852
+ )
770
853
  # If there is no custom waiter found for the provided name,
771
854
  # then try checking the service's official waiters.
772
855
  return self.conn.get_waiter(waiter_name)
@@ -935,7 +1018,7 @@ class BaseAsyncSessionFactory(BaseSessionFactory):
935
1018
  aio_session.set_config_variable("region", region_name)
936
1019
  return aio_session
937
1020
 
938
- def create_session(self) -> AioSession:
1021
+ def create_session(self, deferrable: bool = False) -> AioSession:
939
1022
  """Create aiobotocore Session from connection and config."""
940
1023
  if not self._conn:
941
1024
  self.log.info("No connection ID provided. Fallback on boto3 credential strategy")
@@ -138,7 +138,9 @@ class BatchWaitersHook(BatchClientHook):
138
138
  """
139
139
  return self._waiter_model
140
140
 
141
- def get_waiter(self, waiter_name: str, _: dict[str, str] | None = None) -> botocore.waiter.Waiter:
141
+ def get_waiter(
142
+ self, waiter_name: str, _: dict[str, str] | None = None, deferrable: bool = False, client=None
143
+ ) -> botocore.waiter.Waiter:
142
144
  """
143
145
  Get an AWS Batch service waiter, using the configured ``.waiter_model``.
144
146
 
@@ -24,7 +24,6 @@ from typing import Any
24
24
 
25
25
  from botocore.exceptions import ClientError
26
26
 
27
- from airflow.compat.functools import cached_property
28
27
  from airflow.exceptions import AirflowException, AirflowNotFoundException
29
28
  from airflow.providers.amazon.aws.hooks.base_aws import AwsBaseHook
30
29
  from airflow.utils.helpers import prune_dict
@@ -253,10 +252,41 @@ class EmrServerlessHook(AwsBaseHook):
253
252
  kwargs["client_type"] = "emr-serverless"
254
253
  super().__init__(*args, **kwargs)
255
254
 
256
- @cached_property
257
- def conn(self):
258
- """Get the underlying boto3 EmrServerlessAPIService client (cached)"""
259
- return super().conn
255
+ def cancel_running_jobs(self, application_id: str, waiter_config: dict = {}):
256
+ """
257
+ List all jobs in an intermediate state and cancel them.
258
+ Then wait for those jobs to reach a terminal state.
259
+ Note: if new jobs are triggered while this operation is ongoing,
260
+ it's going to time out and return an error.
261
+ """
262
+ paginator = self.conn.get_paginator("list_job_runs")
263
+ results_per_response = 50
264
+ iterator = paginator.paginate(
265
+ applicationId=application_id,
266
+ states=list(self.JOB_INTERMEDIATE_STATES),
267
+ PaginationConfig={
268
+ "PageSize": results_per_response,
269
+ },
270
+ )
271
+ count = 0
272
+ for r in iterator:
273
+ job_ids = [jr["id"] for jr in r["jobRuns"]]
274
+ count += len(job_ids)
275
+ if len(job_ids) > 0:
276
+ self.log.info(
277
+ "Cancelling %s pending job(s) for the application %s so that it can be stopped",
278
+ len(job_ids),
279
+ application_id,
280
+ )
281
+ for job_id in job_ids:
282
+ self.conn.cancel_job_run(applicationId=application_id, jobRunId=job_id)
283
+ if count > 0:
284
+ self.log.info("now waiting for the %s cancelled job(s) to terminate", count)
285
+ self.get_waiter("no_job_running").wait(
286
+ applicationId=application_id,
287
+ states=list(self.JOB_INTERMEDIATE_STATES.union({"CANCELLING"})),
288
+ WaiterConfig=waiter_config,
289
+ )
260
290
 
261
291
 
262
292
  class EmrContainerHook(AwsBaseHook):
@@ -24,9 +24,7 @@ class EmrClusterLink(BaseAwsLink):
24
24
 
25
25
  name = "EMR Cluster"
26
26
  key = "emr_cluster"
27
- format_str = (
28
- BASE_AWS_CONSOLE_LINK + "/elasticmapreduce/home?region={region_name}#cluster-details:{job_flow_id}"
29
- )
27
+ format_str = BASE_AWS_CONSOLE_LINK + "/emr/home?region={region_name}#/clusterDetails/{job_flow_id}"
30
28
 
31
29
 
32
30
  class EmrLogsLink(BaseAwsLink):
@@ -1025,6 +1025,10 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
1025
1025
  the application be stopped. Defaults to 5 minutes.
1026
1026
  :param waiter_check_interval_seconds: Number of seconds between polling the state of the application.
1027
1027
  Defaults to 30 seconds.
1028
+ :param force_stop: If set to True, any job for that app that is not in a terminal state will be cancelled.
1029
+ Otherwise, trying to stop an app with running jobs will return an error.
1030
+ If you want to wait for the jobs to finish gracefully, use
1031
+ :class:`airflow.providers.amazon.aws.sensors.emr.EmrServerlessJobSensor`
1028
1032
  """
1029
1033
 
1030
1034
  template_fields: Sequence[str] = ("application_id",)
@@ -1036,6 +1040,7 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
1036
1040
  aws_conn_id: str = "aws_default",
1037
1041
  waiter_countdown: int = 5 * 60,
1038
1042
  waiter_check_interval_seconds: int = 30,
1043
+ force_stop: bool = False,
1039
1044
  **kwargs,
1040
1045
  ):
1041
1046
  self.aws_conn_id = aws_conn_id
@@ -1043,6 +1048,7 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
1043
1048
  self.wait_for_completion = wait_for_completion
1044
1049
  self.waiter_countdown = waiter_countdown
1045
1050
  self.waiter_check_interval_seconds = waiter_check_interval_seconds
1051
+ self.force_stop = force_stop
1046
1052
  super().__init__(**kwargs)
1047
1053
 
1048
1054
  @cached_property
@@ -1052,6 +1058,16 @@ class EmrServerlessStopApplicationOperator(BaseOperator):
1052
1058
 
1053
1059
  def execute(self, context: Context) -> None:
1054
1060
  self.log.info("Stopping application: %s", self.application_id)
1061
+
1062
+ if self.force_stop:
1063
+ self.hook.cancel_running_jobs(
1064
+ self.application_id,
1065
+ waiter_config={
1066
+ "Delay": self.waiter_check_interval_seconds,
1067
+ "MaxAttempts": self.waiter_countdown / self.waiter_check_interval_seconds,
1068
+ },
1069
+ )
1070
+
1055
1071
  self.hook.conn.stop_application(applicationId=self.application_id)
1056
1072
 
1057
1073
  if self.wait_for_completion:
@@ -1088,6 +1104,10 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
1088
1104
  the application to be stopped, and then deleted. Defaults to 25 minutes.
1089
1105
  :param waiter_check_interval_seconds: Number of seconds between polling the state of the application.
1090
1106
  Defaults to 60 seconds.
1107
+ :param force_stop: If set to True, any job for that app that is not in a terminal state will be cancelled.
1108
+ Otherwise, trying to delete an app with running jobs will return an error.
1109
+ If you want to wait for the jobs to finish gracefully, use
1110
+ :class:`airflow.providers.amazon.aws.sensors.emr.EmrServerlessJobSensor`
1091
1111
  """
1092
1112
 
1093
1113
  template_fields: Sequence[str] = ("application_id",)
@@ -1099,6 +1119,7 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
1099
1119
  aws_conn_id: str = "aws_default",
1100
1120
  waiter_countdown: int = 25 * 60,
1101
1121
  waiter_check_interval_seconds: int = 60,
1122
+ force_stop: bool = False,
1102
1123
  **kwargs,
1103
1124
  ):
1104
1125
  self.wait_for_delete_completion = wait_for_completion
@@ -1110,6 +1131,7 @@ class EmrServerlessDeleteApplicationOperator(EmrServerlessStopApplicationOperato
1110
1131
  aws_conn_id=aws_conn_id,
1111
1132
  waiter_countdown=waiter_countdown,
1112
1133
  waiter_check_interval_seconds=waiter_check_interval_seconds,
1134
+ force_stop=force_stop,
1113
1135
  **kwargs,
1114
1136
  )
1115
1137
 
@@ -80,7 +80,7 @@ class RdsCreateDbSnapshotOperator(RdsBaseOperator):
80
80
  db_snapshot_identifier: str,
81
81
  tags: Sequence[TagTypeDef] | dict | None = None,
82
82
  wait_for_completion: bool = True,
83
- aws_conn_id: str = "aws_conn_id",
83
+ aws_conn_id: str = "aws_default",
84
84
  **kwargs,
85
85
  ):
86
86
  super().__init__(aws_conn_id=aws_conn_id, **kwargs)
@@ -22,7 +22,10 @@ from typing import TYPE_CHECKING, Any, Sequence
22
22
  from airflow.exceptions import AirflowException
23
23
  from airflow.models import BaseOperator
24
24
  from airflow.providers.amazon.aws.hooks.redshift_cluster import RedshiftHook
25
- from airflow.providers.amazon.aws.triggers.redshift_cluster import RedshiftClusterTrigger
25
+ from airflow.providers.amazon.aws.triggers.redshift_cluster import (
26
+ RedshiftClusterTrigger,
27
+ RedshiftCreateClusterTrigger,
28
+ )
26
29
 
27
30
  if TYPE_CHECKING:
28
31
  from airflow.utils.context import Context
@@ -88,6 +91,7 @@ class RedshiftCreateClusterOperator(BaseOperator):
88
91
  :param wait_for_completion: Whether wait for the cluster to be in ``available`` state
89
92
  :param max_attempt: The maximum number of attempts to be made. Default: 5
90
93
  :param poll_interval: The amount of time in seconds to wait between attempts. Default: 60
94
+ :param deferrable: If True, the operator will run in deferrable mode
91
95
  """
92
96
 
93
97
  template_fields: Sequence[str] = (
@@ -140,6 +144,7 @@ class RedshiftCreateClusterOperator(BaseOperator):
140
144
  wait_for_completion: bool = False,
141
145
  max_attempt: int = 5,
142
146
  poll_interval: int = 60,
147
+ deferrable: bool = False,
143
148
  **kwargs,
144
149
  ):
145
150
  super().__init__(**kwargs)
@@ -180,6 +185,7 @@ class RedshiftCreateClusterOperator(BaseOperator):
180
185
  self.wait_for_completion = wait_for_completion
181
186
  self.max_attempt = max_attempt
182
187
  self.poll_interval = poll_interval
188
+ self.deferrable = deferrable
183
189
  self.kwargs = kwargs
184
190
 
185
191
  def execute(self, context: Context):
@@ -252,6 +258,16 @@ class RedshiftCreateClusterOperator(BaseOperator):
252
258
  self.master_user_password,
253
259
  params,
254
260
  )
261
+ if self.deferrable:
262
+ self.defer(
263
+ trigger=RedshiftCreateClusterTrigger(
264
+ cluster_identifier=self.cluster_identifier,
265
+ poll_interval=self.poll_interval,
266
+ max_attempt=self.max_attempt,
267
+ aws_conn_id=self.aws_conn_id,
268
+ ),
269
+ method_name="execute_complete",
270
+ )
255
271
  if self.wait_for_completion:
256
272
  redshift_hook.get_conn().get_waiter("cluster_available").wait(
257
273
  ClusterIdentifier=self.cluster_identifier,
@@ -264,6 +280,11 @@ class RedshiftCreateClusterOperator(BaseOperator):
264
280
  self.log.info("Created Redshift cluster %s", self.cluster_identifier)
265
281
  self.log.info(cluster)
266
282
 
283
+ def execute_complete(self, context, event=None):
284
+ if event["status"] != "success":
285
+ raise AirflowException(f"Error creating cluster: {event}")
286
+ return
287
+
267
288
 
268
289
  class RedshiftCreateClusterSnapshotOperator(BaseOperator):
269
290
  """
@@ -87,7 +87,7 @@ class DynamoDBToS3Operator(AwsToAwsBaseOperator):
87
87
  :param dynamodb_scan_kwargs: kwargs pass to <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.scan>
88
88
  :param s3_key_prefix: Prefix of s3 object key
89
89
  :param process_func: How we transforms a dynamodb item to bytes. By default we dump the json
90
- """ # noqa: E501
90
+ """
91
91
 
92
92
  template_fields: Sequence[str] = (
93
93
  *AwsToAwsBaseOperator.template_fields,
@@ -18,7 +18,8 @@ from __future__ import annotations
18
18
 
19
19
  from typing import Any, AsyncIterator
20
20
 
21
- from airflow.providers.amazon.aws.hooks.redshift_cluster import RedshiftAsyncHook
21
+ from airflow.compat.functools import cached_property
22
+ from airflow.providers.amazon.aws.hooks.redshift_cluster import RedshiftAsyncHook, RedshiftHook
22
23
  from airflow.triggers.base import BaseTrigger, TriggerEvent
23
24
 
24
25
 
@@ -55,7 +56,7 @@ class RedshiftClusterTrigger(BaseTrigger):
55
56
  },
56
57
  )
57
58
 
58
- async def run(self) -> AsyncIterator["TriggerEvent"]:
59
+ async def run(self) -> AsyncIterator[TriggerEvent]:
59
60
  hook = RedshiftAsyncHook(aws_conn_id=self.aws_conn_id)
60
61
  while self.attempts >= 1:
61
62
  self.attempts = self.attempts - 1
@@ -85,3 +86,54 @@ class RedshiftClusterTrigger(BaseTrigger):
85
86
  except Exception as e:
86
87
  if self.attempts < 1:
87
88
  yield TriggerEvent({"status": "error", "message": str(e)})
89
+
90
+
91
+ class RedshiftCreateClusterTrigger(BaseTrigger):
92
+ """
93
+ Trigger for RedshiftCreateClusterOperator.
94
+ The trigger will asynchronously poll the boto3 API and wait for the
95
+ Redshift cluster to be in the `available` state.
96
+
97
+ :param cluster_identifier: A unique identifier for the cluster.
98
+ :param poll_interval: The amount of time in seconds to wait between attempts.
99
+ :param max_attempt: The maximum number of attempts to be made.
100
+ :param aws_conn_id: The Airflow connection used for AWS credentials.
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ cluster_identifier: str,
106
+ poll_interval: int,
107
+ max_attempt: int,
108
+ aws_conn_id: str,
109
+ ):
110
+ self.cluster_identifier = cluster_identifier
111
+ self.poll_interval = poll_interval
112
+ self.max_attempt = max_attempt
113
+ self.aws_conn_id = aws_conn_id
114
+
115
+ def serialize(self) -> tuple[str, dict[str, Any]]:
116
+ return (
117
+ "airflow.providers.amazon.aws.triggers.redshift_cluster.RedshiftCreateClusterTrigger",
118
+ {
119
+ "cluster_identifier": str(self.cluster_identifier),
120
+ "poll_interval": str(self.poll_interval),
121
+ "max_attempt": str(self.max_attempt),
122
+ "aws_conn_id": str(self.aws_conn_id),
123
+ },
124
+ )
125
+
126
+ @cached_property
127
+ def hook(self) -> RedshiftHook:
128
+ return RedshiftHook(aws_conn_id=self.aws_conn_id)
129
+
130
+ async def run(self):
131
+ async with self.hook.async_conn as client:
132
+ await client.get_waiter("cluster_available").wait(
133
+ ClusterIdentifier=self.cluster_identifier,
134
+ WaiterConfig={
135
+ "Delay": int(self.poll_interval),
136
+ "MaxAttempts": int(self.max_attempt),
137
+ },
138
+ )
139
+ yield TriggerEvent({"status": "success", "message": "Cluster Created"})
@@ -28,9 +28,20 @@ class BaseBotoWaiter:
28
28
  For more details, see airflow/providers/amazon/aws/waiters/README.md
29
29
  """
30
30
 
31
- def __init__(self, client: boto3.client, model_config: dict) -> None:
31
+ def __init__(self, client: boto3.client, model_config: dict, deferrable: bool = False) -> None:
32
32
  self.model = WaiterModel(model_config)
33
33
  self.client = client
34
+ self.deferrable = deferrable
35
+
36
+ def _get_async_waiter_with_client(self, waiter_name: str):
37
+ from aiobotocore.waiter import create_waiter_with_client as create_async_waiter_with_client
38
+
39
+ return create_async_waiter_with_client(
40
+ waiter_name=waiter_name, waiter_model=self.model, client=self.client
41
+ )
34
42
 
35
43
  def waiter(self, waiter_name: str) -> Waiter:
44
+ if self.deferrable:
45
+ return self._get_async_waiter_with_client(waiter_name=waiter_name)
46
+
36
47
  return create_waiter_with_client(waiter_name=waiter_name, waiter_model=self.model, client=self.client)
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 2,
3
+ "waiters": {
4
+ "no_job_running": {
5
+ "operation": "ListJobRuns",
6
+ "delay": 10,
7
+ "maxAttempts": 60,
8
+ "acceptors": [
9
+ {
10
+ "matcher": "path",
11
+ "argument": "length(jobRuns) == `0`",
12
+ "expected": true,
13
+ "state": "success"
14
+ }
15
+ ]
16
+ }
17
+ }
18
+ }
@@ -74,6 +74,7 @@ def get_provider_info():
74
74
  "mypy-boto3-rds>=1.24.0",
75
75
  "mypy-boto3-redshift-data>=1.24.0",
76
76
  "mypy-boto3-appflow>=1.24.0",
77
+ "aiobotocore[boto3]>=2.2.0",
77
78
  ],
78
79
  "integrations": [
79
80
  {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apache-airflow-providers-amazon
3
- Version: 8.0.0rc1
3
+ Version: 8.0.0rc2
4
4
  Summary: Provider for Apache Airflow. Implements apache-airflow-providers-amazon package
5
5
  Home-page: https://airflow.apache.org/
6
6
  Download-URL: https://archive.apache.org/dist/airflow/providers
@@ -30,6 +30,7 @@ Requires-Python: ~=3.7
30
30
  Description-Content-Type: text/x-rst
31
31
  License-File: LICENSE
32
32
  License-File: NOTICE
33
+ Requires-Dist: aiobotocore[boto3] (>=2.2.0)
33
34
  Requires-Dist: apache-airflow-providers-common-sql (>=1.3.1.dev0)
34
35
  Requires-Dist: apache-airflow (>=2.3.0.dev0)
35
36
  Requires-Dist: asgiref
@@ -87,7 +88,7 @@ Requires-Dist: apache-airflow-providers-ssh ; extra == 'ssh'
87
88
 
88
89
  Package ``apache-airflow-providers-amazon``
89
90
 
90
- Release: ``8.0.0rc1``
91
+ Release: ``8.0.0rc2``
91
92
 
92
93
 
93
94
  Amazon integration (including `Amazon Web Services (AWS) <https://aws.amazon.com/>`__).
@@ -129,6 +130,7 @@ PIP package Version required
129
130
  ``mypy-boto3-rds`` ``>=1.24.0``
130
131
  ``mypy-boto3-redshift-data`` ``>=1.24.0``
131
132
  ``mypy-boto3-appflow`` ``>=1.24.0``
133
+ ``aiobotocore[boto3]`` ``>=2.2.0``
132
134
  ======================================= ==================
133
135
 
134
136
  Cross provider package dependencies
@@ -199,8 +201,6 @@ Breaking changes
199
201
 
200
202
  Removed deprecated parameter ``max_tries`` from the Athena & EMR hook & operators in favor of ``max_polling_attempts``.
201
203
 
202
- Disabled deprecated behavior of switching to an empty aws connection ID on error. You can set it to None explicitly.
203
-
204
204
  Removed deprecated method ``waiter`` from emr hook in favor of the more generic ``airflow.providers.amazon.aws.utils.waiter.waiter``
205
205
 
206
206
  Removed deprecated unused parameter ``cluster_identifier`` from Redshift Cluster's hook method ``get_cluster_snapshot_status``
@@ -234,6 +234,8 @@ Features
234
234
  * ``add a stop operator to emr serverless (#30720)``
235
235
  * ``SqlToS3Operator - Add feature to partition SQL table (#30460)``
236
236
  * ``New AWS sensor — DynamoDBValueSensor (#28338)``
237
+ * ``Add a "force" option to emr serverless stop/delete operator (#30757)``
238
+ * ``Add support for deferrable operators in AMPP (#30032)``
237
239
 
238
240
  Bug Fixes
239
241
  ~~~~~~~~~
@@ -250,10 +252,15 @@ Misc
250
252
  * ``Remove @poke_mode_only from EmrStepSensor (#30774)``
251
253
  * ``Organize Amazon providers docs index (#30541)``
252
254
  * ``Remove duplicate param docstring in EksPodOperator (#30634)``
255
+ * ``Update AWS EMR Cluster Link to use the new dashboard (#30844)``
253
256
 
254
257
  .. Below changes are excluded from the changelog. Move them to
255
258
  appropriate section above if needed. Do not delete the lines(!):
256
259
  * ``Decouple "job runner" from BaseJob ORM model (#30255)``
260
+ * ``Upgrade ruff to 0.0.262 (#30809)``
261
+ * ``fixes to system tests following obsolete cleanup (#30804)``
262
+ * ``restore fallback to empty connection behavior (#30806)``
263
+ * ``Prepare docs for adhoc release of providers (#30787)``
257
264
 
258
265
  7.4.1
259
266
  .....
@@ -1,14 +1,14 @@
1
1
  airflow/providers/amazon/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
2
- airflow/providers/amazon/get_provider_info.py,sha256=kviXDXhiXXWsgPFSGd5xgojwyjSzFpMiHhuI1bsW4CI,36826
2
+ airflow/providers/amazon/get_provider_info.py,sha256=gk7Cmp-kyVMF5C5pPpionAknFR7OahOSn3C6u3uf_QI,36867
3
3
  airflow/providers/amazon/aws/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
4
4
  airflow/providers/amazon/aws/exceptions.py,sha256=UVoxpfQEdWI1319h0U78Z_r5wRFQL6DN14hJw3G1Rgo,1731
5
5
  airflow/providers/amazon/aws/hooks/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
6
6
  airflow/providers/amazon/aws/hooks/appflow.py,sha256=oJQSg6kawjjUmbx7YGEK3if_8CNc7g7uk9SRRdxjN0A,4856
7
7
  airflow/providers/amazon/aws/hooks/athena.py,sha256=rFRXRwxxO4hk1kN9rD0fRl9dPnJiLyJhHgyNyWj-yYI,12240
8
- airflow/providers/amazon/aws/hooks/base_aws.py,sha256=Hg2ssKZTM14L00GsmzR3CyTrcZ9HZifshqOPuN7K_ZQ,42181
8
+ airflow/providers/amazon/aws/hooks/base_aws.py,sha256=7xpTAxrZtVba0_Hdo0nefjVeZjbVAs8t_fwpFTRH4SM,45421
9
9
  airflow/providers/amazon/aws/hooks/batch_client.py,sha256=NmVxHHK4FMOhCHqbAddsVeeDlWlmVBBFImxCOWUTqow,20629
10
10
  airflow/providers/amazon/aws/hooks/batch_waiters.json,sha256=eoN5YDgeTNZ2Xz17TrbKBPhd7z9-6KD3RhaDKXXOvqU,2511
11
- airflow/providers/amazon/aws/hooks/batch_waiters.py,sha256=IljZE22oJdiZ6wRbJha_w5mbGhyNUXZzMqwQ1j6Y0NE,9644
11
+ airflow/providers/amazon/aws/hooks/batch_waiters.py,sha256=u-PlSje_eIxcI-brKrUj8JDEExZg2nyU2h10Cl4O0-I,9697
12
12
  airflow/providers/amazon/aws/hooks/cloud_formation.py,sha256=1IY_btHjNZJr5yMPCo3nYBMJKIHO5z60ywaRqm2irUs,3376
13
13
  airflow/providers/amazon/aws/hooks/datasync.py,sha256=AAFLNqrrCwumDaHPtG8dluzd7pp5jXQK4wkP_6rs88M,14133
14
14
  airflow/providers/amazon/aws/hooks/dms.py,sha256=iPQ9lyQcJLmD_mv8OYi8JihDDyQ-PWIcHUFJBl3jueA,7906
@@ -18,7 +18,7 @@ airflow/providers/amazon/aws/hooks/ecr.py,sha256=OWXMoqKDAmwnwuA_fiVBradMpopqxST
18
18
  airflow/providers/amazon/aws/hooks/ecs.py,sha256=2HxP4a5gUE7aoEWfZrkibDpSgaoBfapRzp4vBTNhnvE,9194
19
19
  airflow/providers/amazon/aws/hooks/eks.py,sha256=AHJr-9gPDa9pISD3vX1hkGXRVaqoc78S4SaD8tIr3_s,23957
20
20
  airflow/providers/amazon/aws/hooks/elasticache_replication_group.py,sha256=5Zr9T8_-Nc-bxvsfrZoWG4GTfaQnL0iYjhFAsYcdnEc,12114
21
- airflow/providers/amazon/aws/hooks/emr.py,sha256=EeVYyLUZJ_v6ikvdZV2nYm4u22N0e3GBV7SrVAazQnc,18990
21
+ airflow/providers/amazon/aws/hooks/emr.py,sha256=E59iIckiK5bf_xLIsRChg2oKMYkjOTQaReTlGlaWYB8,20348
22
22
  airflow/providers/amazon/aws/hooks/glacier.py,sha256=4d8U13e3KCsyf07Zd0H7AkELWQXH8Z6EZhT4X_HxTAw,3518
23
23
  airflow/providers/amazon/aws/hooks/glue.py,sha256=sH2VUuoMKc5gfz5ORRMuvqYsQMz9L9jFyjoWYjdswWY,13816
24
24
  airflow/providers/amazon/aws/hooks/glue_catalog.py,sha256=-GbwxS8wlxr9kQkIkNtqApHriISBntsS-sGEmr4a0Xk,7611
@@ -43,7 +43,7 @@ airflow/providers/amazon/aws/hooks/sts.py,sha256=OcE-Lo-yKvTrocLmok5GMB6qWMDTz2i
43
43
  airflow/providers/amazon/aws/links/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
44
44
  airflow/providers/amazon/aws/links/base_aws.py,sha256=Uj1BjQOfizqpoCGyH0RyhRyf2PRhmWPJ8a3nZrP_jxE,2940
45
45
  airflow/providers/amazon/aws/links/batch.py,sha256=0nqICmZA77YpK0WgTZ6pJUCmOqZxrvJxbxA8ERNV_Qw,1767
46
- airflow/providers/amazon/aws/links/emr.py,sha256=Xg6j4sNAYYLXKL11w9IYbor1pCR__RJVx71QLksZOzg,1445
46
+ airflow/providers/amazon/aws/links/emr.py,sha256=vlZjjPwC2dP9xwX5w64xlcdW0CSFowkUE00_ob4GyAI,1416
47
47
  airflow/providers/amazon/aws/links/glue.py,sha256=sac5NCWhE4yNdUpmHB_OfWYg81rM00BkIKgPGnyplCE,1228
48
48
  airflow/providers/amazon/aws/links/logs.py,sha256=BWIU7YQxKt03sUHukr2Z9T99Dzxmwi3pyxHBlWPOi00,1607
49
49
  airflow/providers/amazon/aws/log/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
@@ -59,14 +59,14 @@ airflow/providers/amazon/aws/operators/dms.py,sha256=WvLNzBYglB8iKxEHu3eD2NbJ1N8
59
59
  airflow/providers/amazon/aws/operators/ec2.py,sha256=iM-DP-RRIYIbIL1mkVr3Eo61U4s6wCBdps_m465mfNg,9589
60
60
  airflow/providers/amazon/aws/operators/ecs.py,sha256=1oZSO52u0DrzkRbtWWHHek7SrloipMl7z3HomxPlZYY,29018
61
61
  airflow/providers/amazon/aws/operators/eks.py,sha256=QlZCVymQPZUdJw91B31s5VNc3kmO_H-G6InSpsSAOeA,31753
62
- airflow/providers/amazon/aws/operators/emr.py,sha256=1W_SZ0kZDFoN9EUoYHcYQCzXZZNfyUMgFtbLLrqJBnE,49023
62
+ airflow/providers/amazon/aws/operators/emr.py,sha256=EDeZwfJdAAR3pkea0jqm6SXunITd_fhcmNvTpHswqRo,50180
63
63
  airflow/providers/amazon/aws/operators/glacier.py,sha256=FY07RQgiYuJI6wfrnXFzShvVRfQh9Qe4MJKh3lJGPY4,3705
64
64
  airflow/providers/amazon/aws/operators/glue.py,sha256=cw9_5TVLLXargUtgNfn4OHDrx1GPU_F_PsIEI9QNfgw,7471
65
65
  airflow/providers/amazon/aws/operators/glue_crawler.py,sha256=l1KbD59PO9RlGm_eCUrkIOP76ZvxHvZa0152vlkoY_I,3306
66
66
  airflow/providers/amazon/aws/operators/lambda_function.py,sha256=aFWRwBa5FgGpKGS402QWtwOdetg8xXwHIRPfwCmoelU,7755
67
67
  airflow/providers/amazon/aws/operators/quicksight.py,sha256=ZzppR2QONsxmOjTDHHOFFqL7SwPKXEVccoC2-EoWu9A,3972
68
- airflow/providers/amazon/aws/operators/rds.py,sha256=UPtw9SgzaWZFMiSNJt5ab8rUSfFhUIDDqxZJ3JDjMB0,29868
69
- airflow/providers/amazon/aws/operators/redshift_cluster.py,sha256=Dgu0Br1kS0sLBiYcQ4uIlcM-5Lsog_IZHpqrzhAmPIU,28218
68
+ airflow/providers/amazon/aws/operators/rds.py,sha256=uhNAzp5bCvbnYAaIRVUSwGu-uYDCfZuLc72zqQSD8ok,29868
69
+ airflow/providers/amazon/aws/operators/redshift_cluster.py,sha256=Ay_JHBWQnHwraFRSCXqXAuCaqEDj551lrW6lVYVKYq4,28991
70
70
  airflow/providers/amazon/aws/operators/redshift_data.py,sha256=gy3pkrlMKl1X5KHAy9OyiOVUd4F1pOAwA5xAFztKTgY,5559
71
71
  airflow/providers/amazon/aws/operators/s3.py,sha256=xHMdAktBsz73tm9D-x3bn-YH-O_7nJommmfxE8hsYtM,29966
72
72
  airflow/providers/amazon/aws/operators/sagemaker.py,sha256=d_T4y0AnCt06QhojahsSoh4b1_84Bc3r6hZvupZX_lo,49835
@@ -100,7 +100,7 @@ airflow/providers/amazon/aws/sensors/sqs.py,sha256=3q4ADyIxg3ad4eJDN7iS5DE0NZgSj
100
100
  airflow/providers/amazon/aws/sensors/step_function.py,sha256=viLmKvH66ay9D5NBCYIYgYISKSSuwfl60doEYiNCcmY,3400
101
101
  airflow/providers/amazon/aws/transfers/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
102
102
  airflow/providers/amazon/aws/transfers/base.py,sha256=J1_Zfu4l5hD6d2YscGxk9fIBa6VlBiz8Pa2Bv8lE_zY,2768
103
- airflow/providers/amazon/aws/transfers/dynamodb_to_s3.py,sha256=2mooMubDviUIxvM-50-wocqBP4Rhbh4RzYYEm4-B4TU,5857
103
+ airflow/providers/amazon/aws/transfers/dynamodb_to_s3.py,sha256=mdstDij3G6VyGxvjHYaZuXR1SIV_7PVpCi_NEoLtduY,5843
104
104
  airflow/providers/amazon/aws/transfers/exasol_to_s3.py,sha256=Hwh684UFRIfhYxHlapNuAiIse770ZmeB8Le_KMd6o04,4410
105
105
  airflow/providers/amazon/aws/transfers/ftp_to_s3.py,sha256=tj9W9432lBcbty6bpB5lt_6OccsDieTRWCZiZBJ7k74,6435
106
106
  airflow/providers/amazon/aws/transfers/gcs_to_s3.py,sha256=4uQMPZBxbi8j9sFZ3KhoanbNx-JoNfFqU3w4C_yg_k0,7676
@@ -119,7 +119,7 @@ airflow/providers/amazon/aws/transfers/salesforce_to_s3.py,sha256=3sYK78G9vHPv9g
119
119
  airflow/providers/amazon/aws/transfers/sftp_to_s3.py,sha256=zlgbY6C9JzZCDaZT9xOU8J4_hO7oW16g_OQjSuOH5u4,3679
120
120
  airflow/providers/amazon/aws/transfers/sql_to_s3.py,sha256=NktjDIdFoU0T7FQ_ZlfqAtPClEgktCleur0vVwDLmks,8475
121
121
  airflow/providers/amazon/aws/triggers/__init__.py,sha256=mlJxuZLkd5x-iq2SBwD3mvRQpt3YR7wjz_nceyF1IaI,787
122
- airflow/providers/amazon/aws/triggers/redshift_cluster.py,sha256=zzgVK4c88MUbEbp4mNsy1G_OQrlD0C3-vuXPNF8-7is,3640
122
+ airflow/providers/amazon/aws/triggers/redshift_cluster.py,sha256=gayq9dNO265b0a9xfuGlZL7rpix6gSGemGiR20xkpQE,5579
123
123
  airflow/providers/amazon/aws/utils/__init__.py,sha256=-cre0aYXXsfV5D5an_oLOphKhr-k_uh-dYPvteOrTaE,2215
124
124
  airflow/providers/amazon/aws/utils/connection_wrapper.py,sha256=rM3PzrHJlrT4QczMIeOhJxw9Js6tGOizDLUepzb5kvA,20348
125
125
  airflow/providers/amazon/aws/utils/eks_get_token.py,sha256=HkntWy4xaASjPRGj2of1leLDyDK4JEwRctqOD4nvOt4,2561
@@ -131,14 +131,15 @@ airflow/providers/amazon/aws/utils/tags.py,sha256=apnh_6MBcNzSCNPXW5BqFjJwgih_t2
131
131
  airflow/providers/amazon/aws/utils/waiter.py,sha256=gMtCiIBINvE0wSMzjpDwZw7VMzhGt4do6CoNAR-z1wE,3583
132
132
  airflow/providers/amazon/aws/waiters/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785
133
133
  airflow/providers/amazon/aws/waiters/appflow.json,sha256=9Ye1dItlQGd9fWX63GriQ_VAYbF16cSGcXy4MxU5MGg,1002
134
- airflow/providers/amazon/aws/waiters/base_waiter.py,sha256=wIZgp3KG54qqldXs2hIcjsI-TDVGbSiVm6fF47RvMYE,1378
134
+ airflow/providers/amazon/aws/waiters/base_waiter.py,sha256=4hEgo0BvSpwlKX1Jo3eQqc43hQA_FVKAN82gGlWat1g,1853
135
135
  airflow/providers/amazon/aws/waiters/ecs.json,sha256=q1_9vRtdZG0g86HHMvkjmBtC2CNaMujA0fzMRGrCLxQ,2720
136
136
  airflow/providers/amazon/aws/waiters/eks.json,sha256=8Pl_P_KjEXkrKrA3Enzux4ve_SA8acvvV5Dch8YLgIU,659
137
+ airflow/providers/amazon/aws/waiters/emr-serverless.json,sha256=EiqhDMZLkY101m-9KMbphDQLPXydBRKqXrDJjpjjZsw,430
137
138
  airflow/providers/amazon/aws/waiters/emr.json,sha256=_h2F-sZ2DJGYfV9Nj0RD8Bl5m02Z09MO6X_U8bgmOjA,2559
138
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
139
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/METADATA,sha256=HW344Kuq3KNg8Nm0xV1fG354gVq0iZP0o4jMbHO1_x8,57941
140
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/NOTICE,sha256=m-6s2XynUxVSUIxO4rVablAZCvFq-wmLrqV91DotRBw,240
141
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
142
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/entry_points.txt,sha256=HNciwBqNoIpFPDUUu9_tL6HyozCAxlpCifH810uQTOQ,103
143
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/top_level.txt,sha256=OeMVH5md7fr2QQWpnZoOWWxWO-0WH1IP70lpTVwopPg,8
144
- apache_airflow_providers_amazon-8.0.0rc1.dist-info/RECORD,,
139
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
140
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/METADATA,sha256=ykU7JcUvyRp6KZNkAZKFg2bC21tgkZa3i5_VfRkWuho,58356
141
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/NOTICE,sha256=m-6s2XynUxVSUIxO4rVablAZCvFq-wmLrqV91DotRBw,240
142
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
143
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/entry_points.txt,sha256=HNciwBqNoIpFPDUUu9_tL6HyozCAxlpCifH810uQTOQ,103
144
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/top_level.txt,sha256=OeMVH5md7fr2QQWpnZoOWWxWO-0WH1IP70lpTVwopPg,8
145
+ apache_airflow_providers_amazon-8.0.0rc2.dist-info/RECORD,,