This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new ab452960931 Fix pickle round-trip of Amazon provider exceptions and
add their tests (#72824)
ab452960931 is described below
commit ab45296093135e96be4df9c10b13194374d9d040
Author: Bingqin Wang <[email protected]>
AuthorDate: Thu Sep 10 02:12:15 2026 -0500
Fix pickle round-trip of Amazon provider exceptions and add their tests
(#72824)
EcsTaskFailToStart.__reduce__ returned a bare string instead of a
one-element tuple, so pickling it has raised PicklingError since #22002.
WaiterTerminalFailure kept last_response outside args, so unpickling it failed
with a missing-argument TypeError. Add a __reduce__ for it in the style of the
ECS exceptions, add the missing test module for aws/exceptions.py, and drop its
OVERLOOKED_TESTS entry.
---
.../tests/unit/always/test_project_structure.py | 1 -
.../src/airflow/providers/amazon/aws/exceptions.py | 7 +-
.../tests/unit/amazon/aws/test_exceptions.py | 137 +++++++++++++++++++++
3 files changed, 143 insertions(+), 2 deletions(-)
diff --git a/airflow-core/tests/unit/always/test_project_structure.py
b/airflow-core/tests/unit/always/test_project_structure.py
index f59a1f77823..509243e21e4 100644
--- a/airflow-core/tests/unit/always/test_project_structure.py
+++ b/airflow-core/tests/unit/always/test_project_structure.py
@@ -72,7 +72,6 @@ class TestProjectStructure:
"providers/amazon/tests/unit/amazon/aws/operators/test_sagemaker.py",
"providers/amazon/tests/unit/amazon/aws/sensors/test_emr.py",
"providers/amazon/tests/unit/amazon/aws/sensors/test_sagemaker.py",
- "providers/amazon/tests/unit/amazon/aws/test_exceptions.py",
"providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker_unified_studio.py",
"providers/amazon/tests/unit/amazon/aws/utils/test_rds.py",
"providers/amazon/tests/unit/amazon/aws/utils/test_sagemaker.py",
diff --git a/providers/amazon/src/airflow/providers/amazon/aws/exceptions.py
b/providers/amazon/src/airflow/providers/amazon/aws/exceptions.py
index 5feb0637e8b..64c15f86189 100644
--- a/providers/amazon/src/airflow/providers/amazon/aws/exceptions.py
+++ b/providers/amazon/src/airflow/providers/amazon/aws/exceptions.py
@@ -34,7 +34,7 @@ class EcsTaskFailToStart(Exception):
def __reduce__(self):
"""Return ECSTask state and its message."""
- return EcsTaskFailToStart, (self.message)
+ return EcsTaskFailToStart, (self.message,)
class EcsOperatorError(Exception):
@@ -115,8 +115,13 @@ class WaiterTerminalFailure(AirflowException):
def __init__(self, message: str, last_response: dict[str, Any]):
super().__init__(message)
+ self.message = message
self.last_response = last_response
+ def __reduce__(self):
+ """Return the waiter failure state as its message and the last waiter
response."""
+ return WaiterTerminalFailure, (self.message, self.last_response)
+
class WaiterMaxAttemptsError(AirflowException):
"""Raised when an AWS waiter exhausts its configured attempts."""
diff --git a/providers/amazon/tests/unit/amazon/aws/test_exceptions.py
b/providers/amazon/tests/unit/amazon/aws/test_exceptions.py
new file mode 100644
index 00000000000..3714019eedb
--- /dev/null
+++ b/providers/amazon/tests/unit/amazon/aws/test_exceptions.py
@@ -0,0 +1,137 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import pickle
+
+import pytest
+
+from airflow.providers.amazon.aws.exceptions import (
+ DataSyncLocationNotFoundError,
+ DataSyncMultipleLocationsError,
+ DataSyncMultipleTasksError,
+ DataSyncTaskCreationError,
+ DataSyncTaskExecutionFailedError,
+ DataSyncTaskNotFoundError,
+ EcsOperatorError,
+ EcsTaskFailToStart,
+ GlueJobRunStoppedError,
+ NeptuneGraphCreationFailedError,
+ NeptuneGraphDeletionFailedError,
+ NeptuneImportTaskCancellationFailedError,
+ NeptuneImportTaskFailedError,
+ NeptunePrivateEndpointCreationFailedError,
+ NeptunePrivateEndpointDeletionFailedError,
+ S3HookPathTraversalError,
+ S3HookUriParseFailure,
+ WaiterMaxAttemptsError,
+ WaiterTerminalFailure,
+)
+from airflow.providers.common.compat.sdk import AirflowException
+
+ECS_FAILURES = [
+ {
+ "arn": "arn:aws:ecs:us-east-1:123456789012:container-instance/abc123",
+ "reason": "RESOURCE:MEMORY",
+ }
+]
+LAST_RESPONSE = {"Cluster": {"Status": "FAILED", "StatusReason": "Insufficient
capacity"}}
+
+AIRFLOW_EXCEPTION_SUBCLASSES = [
+ S3HookUriParseFailure,
+ S3HookPathTraversalError,
+ NeptuneGraphCreationFailedError,
+ NeptunePrivateEndpointCreationFailedError,
+ NeptunePrivateEndpointDeletionFailedError,
+ NeptuneGraphDeletionFailedError,
+ NeptuneImportTaskCancellationFailedError,
+ NeptuneImportTaskFailedError,
+ GlueJobRunStoppedError,
+ DataSyncTaskNotFoundError,
+ DataSyncMultipleTasksError,
+ DataSyncMultipleLocationsError,
+ DataSyncLocationNotFoundError,
+ DataSyncTaskCreationError,
+ DataSyncTaskExecutionFailedError,
+ WaiterMaxAttemptsError,
+]
+
+
+class TestEcsTaskFailToStart:
+ def test_message(self):
+ exc = EcsTaskFailToStart("The task failed to start due to:
OutOfMemoryError")
+
+ assert exc.message == "The task failed to start due to:
OutOfMemoryError"
+ assert str(exc) == "The task failed to start due to: OutOfMemoryError"
+
+ def test_pickle_round_trip(self):
+ exc = EcsTaskFailToStart("The task failed to start due to:
OutOfMemoryError")
+
+ restored = pickle.loads(pickle.dumps(exc))
+
+ assert isinstance(restored, EcsTaskFailToStart)
+ assert restored.message == exc.message
+ assert str(restored) == str(exc)
+
+
+class TestEcsOperatorError:
+ def test_failures_and_message(self):
+ exc = EcsOperatorError(ECS_FAILURES, "ECS could not run the task")
+
+ assert exc.failures == ECS_FAILURES
+ assert exc.message == "ECS could not run the task"
+ assert str(exc) == "ECS could not run the task"
+
+ def test_pickle_round_trip(self):
+ exc = EcsOperatorError(ECS_FAILURES, "ECS could not run the task")
+
+ restored = pickle.loads(pickle.dumps(exc))
+
+ assert isinstance(restored, EcsOperatorError)
+ assert restored.failures == ECS_FAILURES
+ assert restored.message == "ECS could not run the task"
+
+
+class TestWaiterTerminalFailure:
+ def test_message_and_last_response(self):
+ exc = WaiterTerminalFailure("Waiter reached a terminal failure state",
LAST_RESPONSE)
+
+ assert isinstance(exc, AirflowException)
+ assert str(exc) == "Waiter reached a terminal failure state"
+ assert exc.last_response == LAST_RESPONSE
+
+ def test_pickle_round_trip(self):
+ exc = WaiterTerminalFailure("Waiter reached a terminal failure state",
LAST_RESPONSE)
+
+ restored = pickle.loads(pickle.dumps(exc))
+
+ assert isinstance(restored, WaiterTerminalFailure)
+ assert str(restored) == "Waiter reached a terminal failure state"
+ assert restored.last_response == LAST_RESPONSE
+
+
[email protected]("exc_cls", AIRFLOW_EXCEPTION_SUBCLASSES)
+def test_airflow_exception_subclasses_keep_message(exc_cls):
+ exc = exc_cls("something went wrong")
+
+ assert isinstance(exc, AirflowException)
+ assert str(exc) == "something went wrong"
+
+ restored = pickle.loads(pickle.dumps(exc))
+
+ assert isinstance(restored, exc_cls)
+ assert str(restored) == "something went wrong"