This is an automated email from the ASF dual-hosted git repository.
kaxil 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 9df6b79fd35 Stop FileSensor deferring when deferrable is not set
(#73297)
9df6b79fd35 is described below
commit 9df6b79fd35a03ca529c1bb63f2d8485cc654793
Author: Kaxil Naik <[email protected]>
AuthorDate: Fri Sep 18 07:31:42 2026 +0100
Stop FileSensor deferring when deferrable is not set (#73297)
---
.../providers/standard/sensors/filesystem.py | 6 +++++-
.../tests/unit/standard/sensors/test_filesystem.py | 23 ++++++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git
a/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
b/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
index 307c6d901e8..43a2ddde5f8 100644
--- a/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
+++ b/providers/standard/src/airflow/providers/standard/sensors/filesystem.py
@@ -126,8 +126,12 @@ class FileSensor(BaseSensorOperator):
def execute(self, context: Context) -> None:
if not self.deferrable:
+ # The sync path blocks in BaseSensorOperator.execute until poke
succeeds, so the
+ # deferrable branch must not run afterwards: a file consumed
between the two pokes
+ # would otherwise defer a sensor the caller asked not to defer,
and on a deployment
+ # with no triggerer the task then sits in ``deferred`` until
execution_timeout.
super().execute(context=context)
- if not self.poke(context=context):
+ elif not self.poke(context=context):
self.defer(
timeout=datetime.timedelta(seconds=self.timeout),
trigger=FileTrigger(
diff --git a/providers/standard/tests/unit/standard/sensors/test_filesystem.py
b/providers/standard/tests/unit/standard/sensors/test_filesystem.py
index 36ab7719a8f..2e59c0c2c34 100644
--- a/providers/standard/tests/unit/standard/sensors/test_filesystem.py
+++ b/providers/standard/tests/unit/standard/sensors/test_filesystem.py
@@ -21,6 +21,7 @@ import os
import shutil
import tempfile
from datetime import timedelta
+from unittest.mock import patch
import pytest
@@ -258,3 +259,25 @@ class TestFileSensor:
assert second.start_trigger_args.trigger_kwargs["filepath"] ==
second.path
assert first.start_trigger_args.timeout == timedelta(seconds=60)
assert second.start_trigger_args.timeout == timedelta(seconds=999)
+
+ def test_non_deferrable_sensor_does_not_defer_after_the_sync_path(self):
+ """
+ A sensor the caller did not make deferrable must never defer.
+
+ The sync path pokes until the file appears and then returns. Poking a
second time
+ afterwards means a file consumed in between defers the task, and on a
deployment with no
+ triggerer it then sits in ``deferred`` until execution_timeout.
+ """
+ task = FileSensor(
+ task_id="test_no_defer",
+ filepath="temp_dir",
+ fs_conn_id="fs_default",
+ dag=self.dag,
+ timeout=0,
+ )
+
+ # The file is there for the sync path's poke and gone by any second
one.
+ with patch.object(FileSensor, "poke", side_effect=[True, False]) as
mock_poke:
+ task.execute({})
+
+ assert mock_poke.call_count == 1, "the sensor poked again after the
sync path completed"