From d2f064781adb5e8a69655fdf3ee21027fcff12a8 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:40:14 -0400 Subject: [PATCH 1/9] fix/issue-68990: Deprecating poll_interval in ExternalTaskSensor for poke_interval --- .../standard/sensors/external_task.py | 10 +-- .../sensors/test_external_task_sensor.py | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index 8270386754f0a..b331892112143 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -170,7 +170,7 @@ class ExternalTaskSensor(BaseSensorOperator): external_task_id is not None) or check if the DAG to wait for exists (when external_task_id is None), and immediately cease waiting if the external task or DAG does not exist (default value: False). - :param poll_interval: polling period in seconds to check for the status + :param poke_interval: polling period in seconds to check for the status :param deferrable: Run sensor in deferrable mode """ @@ -191,7 +191,7 @@ def __init__( execution_delta: datetime.timedelta | None = None, execution_date_fn: Callable | None = None, check_existence: bool = False, - poll_interval: float = 2.0, + poke_interval: float = 2.0, deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False), **kwargs, ): @@ -263,7 +263,7 @@ def __init__( self.check_existence = check_existence self._has_checked_existence = False self.deferrable = deferrable - self.poll_interval = poll_interval + self.poke_interval = poke_interval self.external_dates_filter: str | None = None def _get_dttm_filter(self, context: Context) -> Sequence[datetime.datetime]: @@ -452,7 +452,7 @@ def execute(self, context: Context) -> None: allowed_states=self.allowed_states, failed_states=self.failed_states, skipped_states=self.skipped_states, - poke_interval=self.poll_interval, + poke_interval=self.poke_interval, soft_fail=self.soft_fail, logical_dates=list(dttm_filter), run_ids=None, @@ -477,7 +477,7 @@ def execute(self, context: Context) -> None: allowed_states=self.allowed_states, failed_states=self.failed_states, skipped_states=self.skipped_states, - poke_interval=self.poll_interval, + poke_interval=self.poke_interval, soft_fail=self.soft_fail, execution_dates=list(dttm_filter), logical_dates=None, diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index ef6bd33a76423..864da08f7a3bb 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1445,6 +1445,46 @@ def test_external_task_sensor_deferrable(self, dag_maker): assert exc.value.trigger.external_task_ids == ["test_task"] assert exc.value.trigger.logical_dates == [DEFAULT_DATE] + def test_poke_interval_set_on_init(self): + """Test that poke_interval is set on init and poll_interval is NOT.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=30, + ) + + assert sensor.poke_interval == 30 + assert not hasattr(sensor, "poll_interval") # No longer supported + + def test_poll_interval_raises_on_init(self): + """Test that the removed poll_interval parameter raises a TypeError on instantiation.""" + with pytest.raises(TypeError, match="Invalid arguments were passed to ExternalTaskSensor"): + ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poll_interval=30, # No longer supported + ) + + @pytest.mark.execution_timeout(10) + def test_deferrable_poke_interval_passed_to_trigger(self, dag_maker): + """Test that poke_interval is correctly forwarded to WorkflowTrigger when deferrable=True.""" + with dag_maker("test_dag_child"): + op = ExternalTaskSensor( + task_id="test_external_task_sensor_check", + external_dag_id="test_dag_parent", + external_task_id="test_task", + deferrable=True, + poke_interval=30, + ) + + with pytest.raises(TaskDeferred) as exc: + op.execute(context=self.context) + + assert isinstance(exc.value.trigger, WorkflowTrigger) + assert exc.value.trigger.poke_interval == 30 + @pytest.mark.execution_timeout(10) def test_external_task_sensor_only_dag_id(self, dag_maker): """Test that the sensor works correctly when only external_dag_id is provided.""" @@ -1550,6 +1590,22 @@ def test_defer_and_fire_task_state_trigger(self): assert isinstance(exc.value.trigger, WorkflowTrigger), "Trigger is not a WorkflowTrigger" + def test_deferrable_poke_interval_passed_to_trigger(self): + """Test that poke_interval flows through to WorkflowTrigger on both AF2 and AF3 paths.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + deferrable=True, + poke_interval=30, + ) + + with pytest.raises(TaskDeferred) as exc: + sensor.execute(context={"execution_date": DEFAULT_DATE, "logical_date": DEFAULT_DATE}) + + assert isinstance(exc.value.trigger, WorkflowTrigger) + assert exc.value.trigger.poke_interval == 30 + def test_defer_and_fire_failed_state_trigger(self): """Tests that an ExternalTaskNotFoundError is raised in case of error event""" sensor = ExternalTaskSensor( @@ -1664,6 +1720,28 @@ def test_defer_execute_complete_re_sets_external_dates_filter_attr(self): assert sensor.external_dates_filter == DEFAULT_DATE.isoformat() + def test_poke_interval_set_on_init(self): + """Test that poke_interval is set on init and poll_interval is NOT.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=30, + ) + + assert sensor.poke_interval == 30 + assert not hasattr(sensor, "poll_interval") # No longer supported + + def test_poll_interval_raises_on_init(self): + """Test that the removed poll_interval parameter raises a TypeError on instantiation.""" + with pytest.raises(TypeError, match="Invalid arguments were passed to ExternalTaskSensor"): + ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poll_interval=30, # No longer supported + ) + @pytest.mark.skipif(not AIRFLOW_V_3_0_PLUS, reason="Needs Flask app context fixture for AF 2") @pytest.mark.parametrize( From 23c592a55e297835ce0b5e393f351b2f9af86f89 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:08:57 -0400 Subject: [PATCH 2/9] fix/issue-68990: Fixing tests --- .../unit/standard/sensors/test_external_task_sensor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index 864da08f7a3bb..49723679dfbd6 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1733,8 +1733,11 @@ def test_poke_interval_set_on_init(self): assert not hasattr(sensor, "poll_interval") # No longer supported def test_poll_interval_raises_on_init(self): - """Test that the removed poll_interval parameter raises a TypeError on instantiation.""" - with pytest.raises(TypeError, match="Invalid arguments were passed to ExternalTaskSensor"): + """Test that the removed poll_interval parameter raises an exception on instantiation.""" + # Determine the exception type based on Airflow version (AF3 removed most usage of AirflowException) + exception_type = TypeError if AIRFLOW_V_3_0_PLUS else AirflowException + + with pytest.raises(exception_type, match="Invalid arguments were passed to ExternalTaskSensor"): ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, From 0e512508d7f46e762cfaf079f3833e4e0abb9fe5 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:55:03 -0400 Subject: [PATCH 3/9] fix/issue-68990: Deprecating poll_interval rather than removing it --- .../standard/sensors/external_task.py | 17 +++- .../sensors/test_external_task_sensor.py | 89 ++++++++++++++----- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index b331892112143..74dd80114e6e0 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -23,6 +23,7 @@ from collections.abc import Callable, Collection, Iterable, Sequence from typing import TYPE_CHECKING, ClassVar +from airflow.exceptions import AirflowProviderDeprecationWarning from airflow.models.dag import DagModel from airflow.providers.common.compat.sdk import ( AirflowSkipException, @@ -171,6 +172,7 @@ class ExternalTaskSensor(BaseSensorOperator): external_task_id is None), and immediately cease waiting if the external task or DAG does not exist (default value: False). :param poke_interval: polling period in seconds to check for the status + :param poll_interval: (DEPRECATED) use ``poke_interval`` instead :param deferrable: Run sensor in deferrable mode """ @@ -191,11 +193,21 @@ def __init__( execution_delta: datetime.timedelta | None = None, execution_date_fn: Callable | None = None, check_existence: bool = False, - poke_interval: float = 2.0, + poke_interval: datetime.timedelta | float = 2.0, + poll_interval: datetime.timedelta | float | None = None, deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False), **kwargs, ): - super().__init__(**kwargs) + if poll_interval is not None: + warnings.warn( + "Parameter `poll_interval` is deprecated and will be removed in a future release. " + "Use `poke_interval` instead.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) + poke_interval = poll_interval + + super().__init__(poke_interval=poke_interval, **kwargs) self.allowed_states: list[str] = ( list(allowed_states) if allowed_states else [TaskInstanceState.SUCCESS.value] @@ -263,7 +275,6 @@ def __init__( self.check_existence = check_existence self._has_checked_existence = False self.deferrable = deferrable - self.poke_interval = poke_interval self.external_dates_filter: str | None = None def _get_dttm_filter(self, context: Context) -> Sequence[datetime.datetime]: diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index 49723679dfbd6..e9f5e88b46e7e 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -27,6 +27,7 @@ from sqlalchemy import select from airflow import settings +from airflow.exceptions import AirflowProviderDeprecationWarning from airflow.models import DagRun, TaskInstance from airflow.models.dag import DAG from airflow.models.serialized_dag import SerializedDagModel @@ -1455,18 +1456,79 @@ def test_poke_interval_set_on_init(self): ) assert sensor.poke_interval == 30 - assert not hasattr(sensor, "poll_interval") # No longer supported + assert not hasattr(sensor, "poll_interval") - def test_poll_interval_raises_on_init(self): - """Test that the removed poll_interval parameter raises a TypeError on instantiation.""" - with pytest.raises(TypeError, match="Invalid arguments were passed to ExternalTaskSensor"): - ExternalTaskSensor( + def test_poke_interval_default_when_unset(self): + """The sensor-specific default of 2.0 must be preserved, not the base sensor's default of 60.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + ) + + assert sensor.poke_interval == 2.0 + + def test_poke_interval_accepts_timedelta(self): + """poke_interval should accept a timedelta, coerced to seconds, same as the base sensor.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=timedelta(seconds=5), + ) + + assert sensor.poke_interval == 5.0 + + def test_poke_interval_no_warning_when_poll_interval_unset(self, recwarn): + """Passing only poke_interval must not emit the poll_interval deprecation warning.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=15, + ) + + assert sensor.poke_interval == 15 + assert not any(issubclass(w.category, AirflowProviderDeprecationWarning) for w in recwarn.list) + + def test_poll_interval_deprecated_and_sets_poke_interval(self): + """poll_interval is deprecated, but its value is still respected as poke_interval.""" + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + sensor = ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, external_dag_id=EXTERNAL_DAG_ID, - poll_interval=30, # No longer supported + poll_interval=45, ) + assert sensor.poke_interval == 45 + assert not hasattr(sensor, "poll_interval") + + def test_poll_interval_zero_still_deprecated(self): + """Regression test: a falsy poll_interval (0) must still take the deprecation path.""" + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poll_interval=0, + ) + + assert sensor.poke_interval == 0 + + def test_poll_interval_overrides_poke_interval_when_both_set(self): + """When both are provided, the deprecated poll_interval takes precedence.""" + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=10, + poll_interval=99, + ) + + assert sensor.poke_interval == 99 + @pytest.mark.execution_timeout(10) def test_deferrable_poke_interval_passed_to_trigger(self, dag_maker): """Test that poke_interval is correctly forwarded to WorkflowTrigger when deferrable=True.""" @@ -1730,20 +1792,7 @@ def test_poke_interval_set_on_init(self): ) assert sensor.poke_interval == 30 - assert not hasattr(sensor, "poll_interval") # No longer supported - - def test_poll_interval_raises_on_init(self): - """Test that the removed poll_interval parameter raises an exception on instantiation.""" - # Determine the exception type based on Airflow version (AF3 removed most usage of AirflowException) - exception_type = TypeError if AIRFLOW_V_3_0_PLUS else AirflowException - - with pytest.raises(exception_type, match="Invalid arguments were passed to ExternalTaskSensor"): - ExternalTaskSensor( - task_id=TASK_ID, - external_task_id=EXTERNAL_TASK_ID, - external_dag_id=EXTERNAL_DAG_ID, - poll_interval=30, # No longer supported - ) + assert not hasattr(sensor, "poll_interval") @pytest.mark.skipif(not AIRFLOW_V_3_0_PLUS, reason="Needs Flask app context fixture for AF 2") From 49c370cee805d08800e7e5b4aaf11450c300576e Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:21:12 -0400 Subject: [PATCH 4/9] fix/issue-68990: Updating default poke_interval --- .../src/airflow/providers/standard/sensors/external_task.py | 2 +- .../tests/unit/standard/sensors/test_external_task_sensor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index 74dd80114e6e0..0cb347f88a508 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -193,7 +193,7 @@ def __init__( execution_delta: datetime.timedelta | None = None, execution_date_fn: Callable | None = None, check_existence: bool = False, - poke_interval: datetime.timedelta | float = 2.0, + poke_interval: datetime.timedelta | float = 60.0, poll_interval: datetime.timedelta | float | None = None, deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False), **kwargs, diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index e9f5e88b46e7e..f894c064475ae 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1459,14 +1459,14 @@ def test_poke_interval_set_on_init(self): assert not hasattr(sensor, "poll_interval") def test_poke_interval_default_when_unset(self): - """The sensor-specific default of 2.0 must be preserved, not the base sensor's default of 60.""" + """The sensor-specific default of 60.0 must be preserved.""" sensor = ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, external_dag_id=EXTERNAL_DAG_ID, ) - assert sensor.poke_interval == 2.0 + assert sensor.poke_interval == 60.0 def test_poke_interval_accepts_timedelta(self): """poke_interval should accept a timedelta, coerced to seconds, same as the base sensor.""" From 0f68298591762b4511684eb58ff3340fb708c1d6 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Wed, 22 Jul 2026 08:23:45 -0400 Subject: [PATCH 5/9] fix/issue-68990: Using BaseSensor poke_interval --- .../src/airflow/providers/standard/sensors/external_task.py | 5 ++--- .../tests/unit/standard/sensors/test_external_task_sensor.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index 0cb347f88a508..eb3d05c7395fc 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -193,7 +193,6 @@ def __init__( execution_delta: datetime.timedelta | None = None, execution_date_fn: Callable | None = None, check_existence: bool = False, - poke_interval: datetime.timedelta | float = 60.0, poll_interval: datetime.timedelta | float | None = None, deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False), **kwargs, @@ -205,9 +204,9 @@ def __init__( AirflowProviderDeprecationWarning, stacklevel=2, ) - poke_interval = poll_interval + kwargs["poke_interval"] = poll_interval - super().__init__(poke_interval=poke_interval, **kwargs) + super().__init__(**kwargs) self.allowed_states: list[str] = ( list(allowed_states) if allowed_states else [TaskInstanceState.SUCCESS.value] diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index f894c064475ae..aba297a6bcfeb 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1459,7 +1459,7 @@ def test_poke_interval_set_on_init(self): assert not hasattr(sensor, "poll_interval") def test_poke_interval_default_when_unset(self): - """The sensor-specific default of 60.0 must be preserved.""" + """The BaseSensor default of 60.0 must be preserved.""" sensor = ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, From 1158c54250c8a326e2e41361d951331026a02d18 Mon Sep 17 00:00:00 2001 From: Jake McGrath <116606359+jroachgolf84@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:58:44 -0400 Subject: [PATCH 6/9] Update providers/standard/src/airflow/providers/standard/sensors/external_task.py Co-authored-by: Phani Kumar <94376113+phanikumv@users.noreply.github.com> --- .../src/airflow/providers/standard/sensors/external_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index eb3d05c7395fc..17a57e717f7c8 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -204,7 +204,7 @@ def __init__( AirflowProviderDeprecationWarning, stacklevel=2, ) - kwargs["poke_interval"] = poll_interval + kwargs.setdefault("poke_interval", poll_interval) super().__init__(**kwargs) From ac41022710cfe3c4a60a9cf61d7f97deeb0409c6 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:22:36 -0400 Subject: [PATCH 7/9] fix/issue-68990: Deprecated property, handling poke and poll --- .../standard/sensors/external_task.py | 21 ++++++++++++++ .../sensors/test_external_task_sensor.py | 29 ++++++++++++++----- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index 17a57e717f7c8..43c8286589f7e 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -276,6 +276,27 @@ def __init__( self.deferrable = deferrable self.external_dates_filter: str | None = None + @property + def poll_interval(self) -> float: + """Deprecated alias for poke_interval.""" + warnings.warn( + "Attribute poll_interval is deprecated and will be removed in a future release. " + "Use poke_interval instead.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) + return self.poke_interval + + @poll_interval.setter + def poll_interval(self, value: float) -> None: + warnings.warn( + "Attribute poll_interval is deprecated and will be removed in a future release. " + "Use poke_interval instead.", + AirflowProviderDeprecationWarning, + stacklevel=2, + ) + self.poke_interval = value + def _get_dttm_filter(self, context: Context) -> Sequence[datetime.datetime]: logical_date = self._get_logical_date(context) diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index aba297a6bcfeb..7a98d2fe93eb3 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1447,7 +1447,7 @@ def test_external_task_sensor_deferrable(self, dag_maker): assert exc.value.trigger.logical_dates == [DEFAULT_DATE] def test_poke_interval_set_on_init(self): - """Test that poke_interval is set on init and poll_interval is NOT.""" + """Test that poke_interval is set on init and the deprecated poll_interval attribute mirrors it.""" sensor = ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, @@ -1456,7 +1456,22 @@ def test_poke_interval_set_on_init(self): ) assert sensor.poke_interval == 30 - assert not hasattr(sensor, "poll_interval") + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + assert sensor.poll_interval == 30 + + def test_poll_interval_attribute_get_set_deprecated(self): + """Reading or writing the poll_interval attribute is deprecated but still mirrors poke_interval.""" + sensor = ExternalTaskSensor( + task_id=TASK_ID, + external_task_id=EXTERNAL_TASK_ID, + external_dag_id=EXTERNAL_DAG_ID, + poke_interval=30, + ) + + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + sensor.poll_interval = 15 + + assert sensor.poke_interval == 15 def test_poke_interval_default_when_unset(self): """The BaseSensor default of 60.0 must be preserved.""" @@ -1502,7 +1517,6 @@ def test_poll_interval_deprecated_and_sets_poke_interval(self): ) assert sensor.poke_interval == 45 - assert not hasattr(sensor, "poll_interval") def test_poll_interval_zero_still_deprecated(self): """Regression test: a falsy poll_interval (0) must still take the deprecation path.""" @@ -1516,7 +1530,7 @@ def test_poll_interval_zero_still_deprecated(self): assert sensor.poke_interval == 0 - def test_poll_interval_overrides_poke_interval_when_both_set(self): + def test_poke_interval_overrides_poll_interval_when_both_set(self): """When both are provided, the deprecated poll_interval takes precedence.""" with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): sensor = ExternalTaskSensor( @@ -1527,7 +1541,7 @@ def test_poll_interval_overrides_poke_interval_when_both_set(self): poll_interval=99, ) - assert sensor.poke_interval == 99 + assert sensor.poke_interval == 10 @pytest.mark.execution_timeout(10) def test_deferrable_poke_interval_passed_to_trigger(self, dag_maker): @@ -1783,7 +1797,7 @@ def test_defer_execute_complete_re_sets_external_dates_filter_attr(self): assert sensor.external_dates_filter == DEFAULT_DATE.isoformat() def test_poke_interval_set_on_init(self): - """Test that poke_interval is set on init and poll_interval is NOT.""" + """Test that poke_interval is set on init and the deprecated poll_interval attribute mirrors it.""" sensor = ExternalTaskSensor( task_id=TASK_ID, external_task_id=EXTERNAL_TASK_ID, @@ -1792,7 +1806,8 @@ def test_poke_interval_set_on_init(self): ) assert sensor.poke_interval == 30 - assert not hasattr(sensor, "poll_interval") + with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): + assert sensor.poll_interval == 30 @pytest.mark.skipif(not AIRFLOW_V_3_0_PLUS, reason="Needs Flask app context fixture for AF 2") From c77986039d5d18c0d3859a09baba15f247d3b609 Mon Sep 17 00:00:00 2001 From: Jake McGrath <116606359+jroachgolf84@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:12:41 -0400 Subject: [PATCH 8/9] Update providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py Co-authored-by: Kaxil Naik --- .../tests/unit/standard/sensors/test_external_task_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py index 7a98d2fe93eb3..3bf470ae33b49 100644 --- a/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py +++ b/providers/standard/tests/unit/standard/sensors/test_external_task_sensor.py @@ -1531,7 +1531,7 @@ def test_poll_interval_zero_still_deprecated(self): assert sensor.poke_interval == 0 def test_poke_interval_overrides_poll_interval_when_both_set(self): - """When both are provided, the deprecated poll_interval takes precedence.""" + """When both are provided, poke_interval takes precedence over the deprecated poll_interval.""" with pytest.warns(AirflowProviderDeprecationWarning, match="poll_interval"): sensor = ExternalTaskSensor( task_id=TASK_ID, From 27ce013f8fd316b64f4ddde096e2932221262a42 Mon Sep 17 00:00:00 2001 From: Jake Roach <116606359+jroachgolf84@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:16:19 -0400 Subject: [PATCH 9/9] fix/issue-68990: Changing type hint for poll_interval --- .../src/airflow/providers/standard/sensors/external_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/standard/src/airflow/providers/standard/sensors/external_task.py b/providers/standard/src/airflow/providers/standard/sensors/external_task.py index 43c8286589f7e..034e86ca2612c 100644 --- a/providers/standard/src/airflow/providers/standard/sensors/external_task.py +++ b/providers/standard/src/airflow/providers/standard/sensors/external_task.py @@ -193,7 +193,7 @@ def __init__( execution_delta: datetime.timedelta | None = None, execution_date_fn: Callable | None = None, check_existence: bool = False, - poll_interval: datetime.timedelta | float | None = None, + poll_interval: float | None = None, deferrable: bool = conf.getboolean("operators", "default_deferrable", fallback=False), **kwargs, ):