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 7b5fc4b7068 Reset Dag autoregistration state when a Python Dag file 
fails to load (#74911)
7b5fc4b7068 is described below

commit 7b5fc4b70688c1bfd612eaec3ad5346c85319303
Author: Shahar Epstein <[email protected]>
AuthorDate: Sat Oct 24 22:28:09 2026 +0300

    Reset Dag autoregistration state when a Python Dag file fails to load 
(#74911)
    
    In parallel test runs, a failed Python Dag import could leave stale state 
that broke later imports in the same worker; the fix always clears it.
---
 .../src/airflow/sdk/importers/python_importer.py   |  8 +++--
 .../task_sdk/importers/test_python_importer.py     | 37 ++++++++++++++++++++++
 2 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/task-sdk/src/airflow/sdk/importers/python_importer.py 
b/task-sdk/src/airflow/sdk/importers/python_importer.py
index e6fae5b4452..502745792f2 100644
--- a/task-sdk/src/airflow/sdk/importers/python_importer.py
+++ b/task-sdk/src/airflow/sdk/importers/python_importer.py
@@ -250,10 +250,9 @@ class 
PythonDagImporter(AbstractDagImporter[FileDagDefinition]):
         if mod_name in sys.modules:
             del sys.modules[mod_name]
 
-        DagContext.current_autoregister_module_name = mod_name
-
         def parse() -> list[types.ModuleType]:
             try:
+                DagContext.current_autoregister_module_name = mod_name
                 with definition.import_context():
                     loader: importlib.abc.Loader
                     if get_file_suffix(definition) == ".pyc":
@@ -285,6 +284,10 @@ class 
PythonDagImporter(AbstractDagImporter[FileDagDefinition]):
                     )
                 )
                 return []
+            finally:
+                # Reset on every exit: DagContext.pop() looks this name up in 
sys.modules, which may not
+                # hold it if the load did not complete.
+                DagContext.current_autoregister_module_name = None
 
         dagbag_import_timeout: float
         try:
@@ -323,7 +326,6 @@ class 
PythonDagImporter(AbstractDagImporter[FileDagDefinition]):
         }
         top_level_dags.update(DagContext.autoregistered_dags)
 
-        DagContext.current_autoregister_module_name = None
         DagContext.autoregistered_dags.clear()
 
         for dag, _mod in top_level_dags:
diff --git a/task-sdk/tests/task_sdk/importers/test_python_importer.py 
b/task-sdk/tests/task_sdk/importers/test_python_importer.py
index 54cfdc94df6..eebbf861916 100644
--- a/task-sdk/tests/task_sdk/importers/test_python_importer.py
+++ b/task-sdk/tests/task_sdk/importers/test_python_importer.py
@@ -33,6 +33,7 @@ from unittest import mock
 
 import pytest
 
+from airflow.sdk import DAG
 from airflow.sdk.exceptions import AirflowConfigException
 from airflow.sdk.importers import (
     DagImportError,
@@ -347,6 +348,42 @@ class TestPythonDagImporter:
                 bundle=mock_bundle,
             )
 
+    @pytest.mark.parametrize(
+        ("timeout_policy", "expectation"),
+        [
+            pytest.param(
+                {"return_value": "invalid_timeout_str"},
+                pytest.raises(AirflowConfigException),
+                id="config-error-propagates",
+            ),
+            pytest.param(
+                {"side_effect": RuntimeError("policy failed")},
+                contextlib.nullcontext(),
+                id="error-reported",
+            ),
+            pytest.param(
+                {"return_value": 0},
+                pytest.raises(KeyboardInterrupt),
+                id="keyboard-interrupt-propagates",
+            ),
+        ],
+    )
+    def test_failed_load_does_not_break_later_dags(self, mock_bundle, 
timeout_policy, expectation):
+        (mock_bundle.path / "dag.py").write_text("raise KeyboardInterrupt\n")
+        mock_settings = mock.MagicMock(spec=["get_dagbag_import_timeout"])
+        
mock_settings.get_dagbag_import_timeout.configure_mock(**timeout_policy)
+        mock_airflow = mock.MagicMock(spec=["settings"], 
settings=mock_settings)
+
+        with mock.patch.dict("sys.modules", {"airflow": mock_airflow}), 
expectation:
+            PythonDagImporter().import_definition(
+                FilesystemDagDefinition(path=mock_bundle.path / "dag.py"),
+                bundle=mock_bundle,
+            )
+
+        with DAG("built_after_failed_load") as dag:
+            pass
+        assert dag.dag_id == "built_after_failed_load"
+
     @mock.patch.object(PythonDagImporter, "_load_modules", 
side_effect=TypeError("unexpected None"))
     def test_unexpected_type_error_captured_in_result_errors(self, mock_load, 
mock_bundle):
         importer = PythonDagImporter()

Reply via email to