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

shahar1 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 9a1273c52b8 Normalize BigQuery DTS sensor expected statuses after 
rendering (#70528)
9a1273c52b8 is described below

commit 9a1273c52b8bcfad135037e7d2280c7d388f11a5
Author: Dr Alex Mitre <[email protected]>
AuthorDate: Tue Sep 22 09:28:53 2026 -0600

    Normalize BigQuery DTS sensor expected statuses after rendering (#70528)
    
    Co-authored-by: Shahar Epstein <[email protected]>
---
 .../providers/google/cloud/sensors/bigquery_dts.py | 23 ++++++++++-----
 .../unit/google/cloud/sensors/test_bigquery_dts.py | 34 ++++++++++++++++++++++
 .../ci/prek/validate_operators_init_exemptions.txt |  1 -
 3 files changed, 49 insertions(+), 9 deletions(-)

diff --git 
a/providers/google/src/airflow/providers/google/cloud/sensors/bigquery_dts.py 
b/providers/google/src/airflow/providers/google/cloud/sensors/bigquery_dts.py
index 6f12f92f36d..05f79d38145 100644
--- 
a/providers/google/src/airflow/providers/google/cloud/sensors/bigquery_dts.py
+++ 
b/providers/google/src/airflow/providers/google/cloud/sensors/bigquery_dts.py
@@ -43,13 +43,14 @@ class 
BigQueryDataTransferServiceTransferRunSensor(BaseSensorOperator):
         For more information on how to use this sensor, take a look at the 
guide:
         :ref:`howto/operator:BigQueryDataTransferServiceTransferRunSensor`
 
-    :param expected_statuses: The expected state of the operation.
+    :param expected_statuses: The expected state of the operation. (templated)
         See:
         
https://cloud.google.com/storage-transfer/docs/reference/rest/v1/transferOperations#Status
-    :param run_id: ID of the transfer run.
-    :param transfer_config_id: ID of transfer config to be used.
+    :param run_id: ID of the transfer run. (templated)
+    :param transfer_config_id: ID of transfer config to be used. (templated)
     :param project_id: The BigQuery project id where the transfer 
configuration should be
         created. If set to None or missing, the default project_id from the 
Google Cloud connection is used.
+        (templated)
     :param retry: A retry object used to retry requests. If `None` is
         specified, requests will not be retried.
     :param request_timeout: The amount of time, in seconds, to wait for the 
request to
@@ -99,7 +100,7 @@ class 
BigQueryDataTransferServiceTransferRunSensor(BaseSensorOperator):
         self.retry = retry
         self.request_timeout = request_timeout
         self.metadata = metadata
-        self.expected_statuses = self._normalize_state_list(expected_statuses)
+        self.expected_statuses = expected_statuses
         self.project_id = project_id
         self.gcp_cloud_conn_id = gcp_conn_id
         self.impersonation_chain = impersonation_chain
@@ -110,9 +111,15 @@ class 
BigQueryDataTransferServiceTransferRunSensor(BaseSensorOperator):
         result = set()
         for state in states:
             if isinstance(state, str):
-                # The proto.Enum type is indexable (via MetaClass and aliased) 
but MyPy is not able to
-                # infer this https://github.com/python/mypy/issues/8968
-                result.add(TransferState[state.upper()])  # type: ignore[misc]
+                try:
+                    # The proto.Enum type is indexable (via MetaClass and 
aliased) but MyPy is not able to
+                    # infer this https://github.com/python/mypy/issues/8968
+                    result.add(TransferState[state.upper()])  # type: 
ignore[misc]
+                except KeyError:
+                    raise ValueError(
+                        f"Invalid expected status {state!r}. "
+                        f"Valid statuses: {sorted(TransferState.__members__)}"
+                    ) from None
             elif isinstance(state, int):
                 result.add(TransferState(state))
             elif isinstance(state, TransferState):
@@ -144,4 +151,4 @@ class 
BigQueryDataTransferServiceTransferRunSensor(BaseSensorOperator):
         if run.state in (TransferState.FAILED, TransferState.CANCELLED):
             message = f"Transfer {self.run_id} did not succeed"
             raise AirflowException(message)
-        return run.state in self.expected_statuses
+        return run.state in self._normalize_state_list(self.expected_statuses)
diff --git 
a/providers/google/tests/unit/google/cloud/sensors/test_bigquery_dts.py 
b/providers/google/tests/unit/google/cloud/sensors/test_bigquery_dts.py
index a0f65811172..514432dd105 100644
--- a/providers/google/tests/unit/google/cloud/sensors/test_bigquery_dts.py
+++ b/providers/google/tests/unit/google/cloud/sensors/test_bigquery_dts.py
@@ -89,3 +89,37 @@ class TestBigQueryDataTransferServiceTransferRunSensor:
             retry=DEFAULT,
             timeout=None,
         )
+
+    @mock.patch(
+        
"airflow.providers.google.cloud.sensors.bigquery_dts.BiqQueryDataTransferServiceHook",
+        
return_value=MM(get_transfer_run=MM(return_value=MM(state=TransferState.SUCCEEDED))),
+    )
+    def test_templated_expected_statuses_rendered_before_poke(self, mock_hook):
+        op = BigQueryDataTransferServiceTransferRunSensor(
+            transfer_config_id=TRANSFER_CONFIG_ID,
+            run_id=RUN_ID,
+            task_id="id",
+            project_id=PROJECT_ID,
+            expected_statuses="{{ expected }}",
+        )
+        assert op.expected_statuses == "{{ expected }}"
+
+        op.render_template_fields({"expected": "succeeded"})
+
+        assert op.poke({}) is True
+
+    @mock.patch(
+        
"airflow.providers.google.cloud.sensors.bigquery_dts.BiqQueryDataTransferServiceHook",
+        
return_value=MM(get_transfer_run=MM(return_value=MM(state=TransferState.SUCCEEDED))),
+    )
+    def test_poke_raises_value_error_for_invalid_expected_status(self, 
mock_hook):
+        op = BigQueryDataTransferServiceTransferRunSensor(
+            transfer_config_id=TRANSFER_CONFIG_ID,
+            run_id=RUN_ID,
+            task_id="id",
+            project_id=PROJECT_ID,
+            expected_statuses="SUCCESS",
+        )
+
+        with pytest.raises(ValueError, match="SUCCESS.*Valid 
statuses.*SUCCEEDED"):
+            op.poke({})
diff --git a/scripts/ci/prek/validate_operators_init_exemptions.txt 
b/scripts/ci/prek/validate_operators_init_exemptions.txt
index 45b4d646fc3..c49a448fcd3 100644
--- a/scripts/ci/prek/validate_operators_init_exemptions.txt
+++ b/scripts/ci/prek/validate_operators_init_exemptions.txt
@@ -11,5 +11,4 @@ 
providers/google/src/airflow/providers/google/cloud/operators/cloud_build.py::Cl
 
providers/google/src/airflow/providers/google/cloud/operators/cloud_storage_transfer_service.py::CloudDataTransferServiceCreateJobOperator
 
providers/google/src/airflow/providers/google/cloud/operators/dataproc.py::DataprocCreateClusterOperator
 
providers/google/src/airflow/providers/google/cloud/operators/functions.py::CloudFunctionDeployFunctionOperator
-providers/google/src/airflow/providers/google/cloud/sensors/bigquery_dts.py::BigQueryDataTransferServiceTransferRunSensor
 
providers/google/src/airflow/providers/google/cloud/sensors/cloud_composer.py::CloudComposerExternalTaskSensor

Reply via email to