This is an automated email from the ASF dual-hosted git repository.

o-nikolas 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 90f064fa244 Allow BedrockBatchInferenceOperator to wait until the job 
is scheduled (#75230)
90f064fa244 is described below

commit 90f064fa244c8d81f29a92a955a7246b506f8f93
Author: Ramit Kataria <[email protected]>
AuthorDate: Wed Oct 21 10:07:16 2026 -0700

    Allow BedrockBatchInferenceOperator to wait until the job is scheduled 
(#75230)
    
    BedrockBatchInferenceSensor can wait for a batch inference job to be either
    scheduled or completed, but the operator could only wait for completion, in
    both wait_for_completion and deferrable mode. Batch inference jobs sit in a
    queue for an unpredictable amount of time, so many Dags only need to know 
that
    Bedrock has accepted the job, and today that takes a separate sensor task. 
The
    waiter and trigger for the scheduled state already exist.
---
 providers/amazon/docs/operators/bedrock.rst        |  3 ++
 .../providers/amazon/aws/operators/bedrock.py      | 27 +++++++++---
 .../unit/amazon/aws/operators/test_bedrock.py      | 48 ++++++++++++++++++++--
 3 files changed, 68 insertions(+), 10 deletions(-)

diff --git a/providers/amazon/docs/operators/bedrock.rst 
b/providers/amazon/docs/operators/bedrock.rst
index 74af2f116a7..5bb49aa891c 100644
--- a/providers/amazon/docs/operators/bedrock.rst
+++ b/providers/amazon/docs/operators/bedrock.rst
@@ -346,6 +346,9 @@ NOTE: Jobs are added to a queue and processed in order.  
Given the potential wai
 and the fact that the optional timeout parameter is measured in hours, 
deferrable mode is
 recommended over "wait_for_completion" in this case.
 
+By default the operator waits for the job to complete. Set 
``success_state="scheduled"`` to stop
+waiting once Bedrock has queued the job. This applies to both 
"wait_for_completion" and deferrable mode.
+
 Example using an Amazon Bedrock Batch Inference Job:
 
 .. exampleinclude:: 
/../../amazon/tests/system/amazon/aws/example_bedrock_batch_inference.py
diff --git 
a/providers/amazon/src/airflow/providers/amazon/aws/operators/bedrock.py 
b/providers/amazon/src/airflow/providers/amazon/aws/operators/bedrock.py
index 3e0f0bc0b7a..fe376405d1e 100644
--- a/providers/amazon/src/airflow/providers/amazon/aws/operators/bedrock.py
+++ b/providers/amazon/src/airflow/providers/amazon/aws/operators/bedrock.py
@@ -36,6 +36,7 @@ from airflow.providers.amazon.aws.triggers.bedrock import (
     BedrockAgentRuntimeDeletedTrigger,
     BedrockAgentRuntimeReadyTrigger,
     BedrockBatchInferenceCompletedTrigger,
+    BedrockBatchInferenceScheduledTrigger,
     BedrockCustomizeModelCompletedTrigger,
     BedrockIngestionJobTrigger,
     BedrockKnowledgeBaseActiveTrigger,
@@ -47,6 +48,7 @@ from airflow.providers.common.compat.sdk import 
AirflowException, conf, timezone
 from airflow.utils.helpers import prune_dict
 
 if TYPE_CHECKING:
+    from airflow.providers.amazon.aws.triggers.bedrock import 
BedrockBaseBatchInferenceTrigger
     from airflow.sdk import Context
 
 
@@ -1239,12 +1241,15 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
     :param output_uri: The S3 location of the output data. (templated)
     :param invoke_kwargs: Additional keyword arguments to pass to the  API 
call. (templated)
 
-    :param wait_for_completion: Whether to wait for cluster to stop. (default: 
True)
+    :param wait_for_completion: Whether to wait for the job to reach 
``success_state``. (default: True)
         NOTE:  The way batch inference jobs work, your jobs are added to a 
queue and done "eventually"
         so using deferrable mode is much more practical than using 
wait_for_completion.
+    :param success_state: The job state to wait for, either ``"completed"`` or 
``"scheduled"``. Use
+        ``"scheduled"`` to stop waiting once Bedrock has queued the job. 
Accepts the
+        ``BedrockBatchInferenceSensor.SuccessState`` values. (default: 
"completed")
     :param waiter_delay: Time in seconds to wait between status checks. 
(default: 60)
     :param waiter_max_attempts: Maximum number of attempts to check for job 
completion. (default: 20)
-    :param deferrable: If True, the operator will wait asynchronously for the 
cluster to stop.
+    :param deferrable: If True, the operator will wait asynchronously for the 
job to reach ``success_state``.
         This implies waiting for completion. This mode requires aiobotocore 
module to be installed.
         (default: False)
     :param aws_conn_id: The Airflow connection used for AWS credentials.
@@ -1268,6 +1273,10 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
         "output_uri",
         "invoke_kwargs",
     )
+    _WAITERS: dict[str, tuple[str, type[BedrockBaseBatchInferenceTrigger]]] = {
+        "completed": ("batch_inference_complete", 
BedrockBatchInferenceCompletedTrigger),
+        "scheduled": ("batch_inference_scheduled", 
BedrockBatchInferenceScheduledTrigger),
+    }
 
     def __init__(
         self,
@@ -1278,6 +1287,7 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
         output_uri: str,
         invoke_kwargs: dict[str, Any] | None = None,
         wait_for_completion: bool = True,
+        success_state: str = "completed",
         waiter_delay: int = 60,
         waiter_max_attempts: int = 20,
         deferrable: bool = conf.getboolean("operators", "default_deferrable", 
fallback=False),
@@ -1291,7 +1301,11 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
         self.output_uri = output_uri
         self.invoke_kwargs = invoke_kwargs or {}
 
+        if success_state not in self._WAITERS:
+            raise ValueError(f"success_state must be one of 
{list(self._WAITERS)}, got {success_state!r}.")
+
         self.wait_for_completion = wait_for_completion
+        self.success_state = success_state
         self.waiter_delay = waiter_delay
         self.waiter_max_attempts = waiter_max_attempts
         self.deferrable = deferrable
@@ -1304,7 +1318,7 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
         if validated_event["status"] != "success":
             raise AirflowException(f"Error while running {self.activity}: 
{validated_event}")
 
-        self.log.info("%s '%s' complete.", self.activity, 
validated_event["job_arn"])
+        self.log.info("%s '%s' is %s.", self.activity, 
validated_event["job_arn"], self.success_state)
 
         return validated_event["job_arn"]
 
@@ -1320,11 +1334,12 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
         job_arn = response["jobArn"]
         self.log.info("%s '%s' started with ARN: %s", self.activity, 
self.job_name, job_arn)
 
-        task_description = f"for {self.activity} '{self.job_name}' to 
complete."
+        waiter_name, trigger_class = self._WAITERS[self.success_state]
+        task_description = f"for {self.activity} '{self.job_name}' to be 
{self.success_state}."
         if self.deferrable:
             self.log.info("Deferring %s", task_description)
             self.defer(
-                trigger=BedrockBatchInferenceCompletedTrigger(
+                trigger=trigger_class(
                     job_arn=job_arn,
                     waiter_delay=self.waiter_delay,
                     waiter_max_attempts=self.waiter_max_attempts,
@@ -1337,7 +1352,7 @@ class 
BedrockBatchInferenceOperator(AwsBaseOperator[BedrockHook]):
             )
         elif self.wait_for_completion:
             self.log.info("Waiting %s", task_description)
-            self.hook.get_waiter(waiter_name="batch_inference_complete").wait(
+            self.hook.get_waiter(waiter_name=waiter_name).wait(
                 jobIdentifier=job_arn,
                 WaiterConfig={"Delay": self.waiter_delay, "MaxAttempts": 
self.waiter_max_attempts},
             )
diff --git a/providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py 
b/providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py
index cae3468a9d9..90468314a61 100644
--- a/providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py
+++ b/providers/amazon/tests/unit/amazon/aws/operators/test_bedrock.py
@@ -53,7 +53,11 @@ from airflow.providers.amazon.aws.operators.bedrock import (
     BedrockRaGOperator,
     BedrockUpdateGuardrailOperator,
 )
-from airflow.providers.amazon.aws.triggers.bedrock import 
BedrockAgentRuntimeDeletedTrigger
+from airflow.providers.amazon.aws.triggers.bedrock import (
+    BedrockAgentRuntimeDeletedTrigger,
+    BedrockBatchInferenceCompletedTrigger,
+    BedrockBatchInferenceScheduledTrigger,
+)
 
 from unit.amazon.aws.utils.test_template_fields import validate_template_fields
 
@@ -1093,8 +1097,8 @@ class TestBedrockBatchInferenceOperator:
             hook = BedrockHook(aws_conn_id="aws_default")
             yield hook
 
-    def setup_method(self):
-        self.operator = BedrockBatchInferenceOperator(
+    def _create_operator(self, **kwargs):
+        operator = BedrockBatchInferenceOperator(
             task_id="test_task",
             job_name=self.JOB_NAME,
             role_arn=self.ROLE_ARN,
@@ -1102,8 +1106,13 @@ class TestBedrockBatchInferenceOperator:
             input_uri=self.INPUT_URI,
             output_uri=self.OUTPUT_URI,
             invoke_kwargs=self.INVOKE_KWARGS,
+            **kwargs,
         )
-        self.operator.defer = mock.MagicMock()
+        operator.defer = mock.MagicMock()
+        return operator
+
+    def setup_method(self):
+        self.operator = self._create_operator()
 
     @pytest.mark.parametrize(
         ("wait_for_completion", "deferrable"),
@@ -1126,6 +1135,37 @@ class TestBedrockBatchInferenceOperator:
         assert bedrock_hook.get_waiter.call_count == wait_for_completion
         assert self.operator.defer.call_count == deferrable
 
+    @pytest.mark.parametrize(
+        ("success_state", "waiter_name"),
+        [
+            pytest.param("completed", "batch_inference_complete", 
id="completed"),
+            pytest.param("scheduled", "batch_inference_scheduled", 
id="scheduled"),
+        ],
+    )
+    @mock.patch.object(BedrockHook, "get_waiter", autospec=True)
+    def test_wait_for_success_state(self, mock_get_waiter, success_state, 
waiter_name, mock_conn):
+        self._create_operator(success_state=success_state).execute({})
+
+        assert mock_get_waiter.call_args.kwargs["waiter_name"] == waiter_name
+
+    @pytest.mark.parametrize(
+        ("success_state", "trigger_class"),
+        [
+            pytest.param("completed", BedrockBatchInferenceCompletedTrigger, 
id="completed"),
+            pytest.param("scheduled", BedrockBatchInferenceScheduledTrigger, 
id="scheduled"),
+        ],
+    )
+    def test_defer_for_success_state(self, success_state, trigger_class, 
mock_conn):
+        operator = self._create_operator(success_state=success_state, 
deferrable=True)
+
+        operator.execute({})
+
+        assert isinstance(operator.defer.call_args.kwargs["trigger"], 
trigger_class)
+
+    def test_invalid_success_state(self):
+        with pytest.raises(ValueError, match="success_state must be one of"):
+            self._create_operator(success_state="running")
+
     def test_template_fields(self):
         validate_template_fields(self.operator)
 

Reply via email to