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 d4622c770f2 Validate AzureVirtualMachineStateSensor target_state after 
rendering (#70329)
d4622c770f2 is described below

commit d4622c770f2094d633987fbce9028bb49baaf91c
Author: Stefan Wang <[email protected]>
AuthorDate: Fri Jul 24 00:43:47 2026 -0700

    Validate AzureVirtualMachineStateSensor target_state after rendering 
(#70329)
    
    Validate AzureVirtualMachineStateSensor target_state after rendering
    target_state is a template field, so it is rendered after __init__ runs. The
    constructor validated it against VALID_STATES and raised ValueError there, 
so a
    templated target_state (e.g. "{{ params.state }}") could never be built: 
__init__
    saw the un-rendered expression and rejected it. Move the check into poke(), 
which
    runs after rendering, and store target_state verbatim in the constructor.
    
    related: #70296
---
 .../providers/microsoft/azure/sensors/compute.py   |  9 ++++----
 .../unit/microsoft/azure/sensors/test_compute.py   | 24 +++++++++++++++-------
 .../ci/prek/validate_operators_init_exemptions.txt |  1 -
 3 files changed, 22 insertions(+), 12 deletions(-)

diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/sensors/compute.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/sensors/compute.py
index 1a9c05185dc..648b111432d 100644
--- 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/sensors/compute.py
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/sensors/compute.py
@@ -60,10 +60,6 @@ class AzureVirtualMachineStateSensor(BaseSensorOperator):
         deferrable: bool = conf.getboolean("operators", "default_deferrable", 
fallback=False),
         **kwargs,
     ) -> None:
-        if target_state not in self.VALID_STATES:
-            raise ValueError(
-                f"Invalid target_state: {target_state}. Must be one of 
{sorted(self.VALID_STATES)}"
-            )
         super().__init__(**kwargs)
         self.resource_group_name = resource_group_name
         self.vm_name = vm_name
@@ -72,6 +68,11 @@ class AzureVirtualMachineStateSensor(BaseSensorOperator):
         self.deferrable = deferrable
 
     def poke(self, context: Context) -> bool:
+        # target_state is a template field; validate the rendered value here, 
not in __init__.
+        if self.target_state not in self.VALID_STATES:
+            raise ValueError(
+                f"Invalid target_state: {self.target_state}. Must be one of 
{sorted(self.VALID_STATES)}"
+            )
         hook = AzureComputeHook(azure_conn_id=self.azure_conn_id)
         current_state = hook.get_power_state(self.resource_group_name, 
self.vm_name)
         self.log.info("VM %s power state: %s", self.vm_name, current_state)
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/sensors/test_compute.py 
b/providers/microsoft/azure/tests/unit/microsoft/azure/sensors/test_compute.py
index 47e4da37a8d..85c168ab89b 100644
--- 
a/providers/microsoft/azure/tests/unit/microsoft/azure/sensors/test_compute.py
+++ 
b/providers/microsoft/azure/tests/unit/microsoft/azure/sensors/test_compute.py
@@ -43,14 +43,24 @@ class TestAzureVirtualMachineStateSensor:
         assert sensor.target_state == "running"
         assert sensor.azure_conn_id == CONN_ID
 
-    def test_init_invalid_target_state(self):
+    def test_invalid_target_state_rejected_at_poke(self):
+        sensor = AzureVirtualMachineStateSensor(
+            task_id="sense_vm",
+            resource_group_name=RESOURCE_GROUP,
+            vm_name=VM_NAME,
+            target_state="invalid_state",
+        )
         with pytest.raises(ValueError, match="Invalid target_state"):
-            AzureVirtualMachineStateSensor(
-                task_id="sense_vm",
-                resource_group_name=RESOURCE_GROUP,
-                vm_name=VM_NAME,
-                target_state="invalid_state",
-            )
+            sensor.poke(context=None)
+
+    def test_templated_target_state_constructs(self):
+        sensor = AzureVirtualMachineStateSensor(
+            task_id="sense_vm",
+            resource_group_name=RESOURCE_GROUP,
+            vm_name=VM_NAME,
+            target_state="{{ params.state }}",
+        )
+        assert sensor.target_state == "{{ params.state }}"
 
     def test_template_fields(self):
         sensor = AzureVirtualMachineStateSensor(
diff --git a/scripts/ci/prek/validate_operators_init_exemptions.txt 
b/scripts/ci/prek/validate_operators_init_exemptions.txt
index ae99a1b3369..669597d0a3d 100644
--- a/scripts/ci/prek/validate_operators_init_exemptions.txt
+++ b/scripts/ci/prek/validate_operators_init_exemptions.txt
@@ -67,7 +67,6 @@ 
providers/google/src/airflow/providers/google/cloud/transfers/gcs_to_bigquery.py
 
providers/google/src/airflow/providers/google/cloud/transfers/gcs_to_gcs.py::GCSToGCSOperator
 
providers/google/src/airflow/providers/google/cloud/transfers/gcs_to_local.py::GCSToLocalFilesystemOperator
 
providers/google/src/airflow/providers/google/marketing_platform/operators/campaign_manager.py::GoogleCampaignManagerDeleteReportOperator
-providers/microsoft/azure/src/airflow/providers/microsoft/azure/sensors/compute.py::AzureVirtualMachineStateSensor
 
providers/microsoft/azure/src/airflow/providers/microsoft/azure/transfers/gcs_to_wasb.py::GCSToAzureBlobStorageOperator
 
providers/microsoft/azure/src/airflow/providers/microsoft/azure/transfers/oracle_to_azure_data_lake.py::OracleToAzureDataLakeOperator
 
providers/microsoft/psrp/src/airflow/providers/microsoft/psrp/operators/psrp.py::PsrpOperator

Reply via email to