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.
- airflow/providers/amazon/aws/hooks/base_aws.py +101 -18
- airflow/providers/amazon/aws/hooks/batch_waiters.py +3 -1
- airflow/providers/amazon/aws/hooks/emr.py +35 -5
- airflow/providers/amazon/aws/links/emr.py +1 -3
- airflow/providers/amazon/aws/operators/emr.py +22 -0
- airflow/providers/amazon/aws/operators/rds.py +1 -1
- airflow/providers/amazon/aws/operators/redshift_cluster.py +22 -1
- airflow/providers/amazon/aws/transfers/dynamodb_to_s3.py +1 -1
- airflow/providers/amazon/aws/triggers/redshift_cluster.py +54 -2
- airflow/providers/amazon/aws/waiters/base_waiter.py +12 -1
- airflow/providers/amazon/aws/waiters/emr-serverless.json +18 -0
- airflow/providers/amazon/get_provider_info.py +1 -0
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/METADATA +11 -4
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/RECORD +19 -18
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/LICENSE +0 -0
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/NOTICE +0 -0
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_amazon-8.0.0rc1.dist-info → apache_airflow_providers_amazon-8.0.0rc2.dist-info}/entry_points.txt +0 -0
- {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
|
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
|
129
|
-
""
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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
|
-
|
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
|
-
|
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(
|
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=
|
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(
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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 = "
|
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
|
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
|
-
"""
|
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.
|
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[
|
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
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apache-airflow-providers-amazon
|
3
|
-
Version: 8.0.
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
69
|
-
airflow/providers/amazon/aws/operators/redshift_cluster.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
139
|
-
apache_airflow_providers_amazon-8.0.
|
140
|
-
apache_airflow_providers_amazon-8.0.
|
141
|
-
apache_airflow_providers_amazon-8.0.
|
142
|
-
apache_airflow_providers_amazon-8.0.
|
143
|
-
apache_airflow_providers_amazon-8.0.
|
144
|
-
apache_airflow_providers_amazon-8.0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|