This is an automated email from the ASF dual-hosted git repository.
jedcunningham pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 8e6ce1240a5 [v3-3-test] Fix nested non-Dag with-statement exiting the
Dag context in version inflation checker (#68795) (#71956)
8e6ce1240a5 is described below
commit 8e6ce1240a5ef9f0053c82748efd31d5e3593ed9
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 25 10:43:34 2026 -0600
[v3-3-test] Fix nested non-Dag with-statement exiting the Dag context in
version inflation checker (#68795) (#71956)
AirflowRuntimeVaryingValueChecker.visit_With() called exit_dag_context()
unconditionally at the end of every with-statement. When a non-DAG
with-statement (e.g. `with open(...) as f`) was nested inside a DAG
with-block, it would set is_in_dag_context=False, causing any tasks
constructed AFTER the nested with — but still inside the DAG block —
to be invisible to the checker. Those tasks were never flagged for
runtime-varying values even when they used datetime.now(), random(), etc.
Fix: guard the exit_dag_context() call with `if is_with_dag_context`
so only the with-statement that actually entered the DAG context exits it.
Regression tests added for:
- nested non-DAG with followed by a task (primary case)
- multiple successive nested non-DAG withs
- task constructed inside a nested non-DAG with
- task outside the DAG block not over-flagged after the fix
(cherry picked from commit c1fcbefd71256cc47cafc2cb989a0ac58838035a)
Co-authored-by: Pradeep Kalluri
<[email protected]>
---
.../airflow/utils/dag_version_inflation_checker.py | 6 +-
.../utils/test_dag_version_inflation_checker.py | 77 ++++++++++++++++++++++
2 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/airflow-core/src/airflow/utils/dag_version_inflation_checker.py
b/airflow-core/src/airflow/utils/dag_version_inflation_checker.py
index 2314b94a126..06fa96a7faa 100644
--- a/airflow-core/src/airflow/utils/dag_version_inflation_checker.py
+++ b/airflow-core/src/airflow/utils/dag_version_inflation_checker.py
@@ -513,8 +513,10 @@ class AirflowRuntimeVaryingValueChecker(ast.NodeVisitor):
for body in node.body:
self.visit(body)
- # Exit Dag with block
- self.dag_detector.exit_dag_context()
+ # Only exit Dag with block if we entered it; unconditional exit would
prematurely
+ # reset the context when a non-DAG with-statement is nested inside a
DAG with block.
+ if is_with_dag_context:
+ self.dag_detector.exit_dag_context()
def visit_FunctionDef(self, node: ast.FunctionDef):
for decorator in node.decorator_list:
diff --git
a/airflow-core/tests/unit/utils/test_dag_version_inflation_checker.py
b/airflow-core/tests/unit/utils/test_dag_version_inflation_checker.py
index 8d858523be4..035375c01f3 100644
--- a/airflow-core/tests/unit/utils/test_dag_version_inflation_checker.py
+++ b/airflow-core/tests/unit/utils/test_dag_version_inflation_checker.py
@@ -812,3 +812,80 @@ with DAG(
"""
warnings = self._check_code(code)
assert len(warnings) == 1
+
+ def test_nested_non_dag_with_does_not_exit_dag_context(self):
+ """Regression test: a non-DAG with-statement nested inside a DAG
with-block must not
+ prematurely exit the DAG context.
+
+ Before the fix, visit_With() called exit_dag_context() unconditionally
regardless of
+ whether a DAG context was entered. This meant any nested non-DAG
with-statement (e.g.,
+ ``with open(...) as f``) would reset is_in_dag_context=False, causing
tasks that appear
+ AFTER the nested with-statement (but still inside the DAG with-block)
to be missed.
+ """
+ code = """
+from airflow import DAG
+from datetime import datetime
+from airflow.operators.bash import BashOperator
+
+with DAG('my_dag') as dag:
+ with open('some_file.txt') as f: # non-DAG with — must not exit DAG
context
+ data = f.read()
+ # This task is still inside the DAG block and uses datetime.now() — must
be flagged
+ t1 = BashOperator(
+ task_id='test',
+ bash_command=str(datetime.now()),
+ )
+"""
+ warnings = self._check_code(code)
+ assert len(warnings) == 1, (
+ "BashOperator after a nested non-DAG with-statement must still be
detected "
+ "as inside the DAG context and flagged for using datetime.now()"
+ )
+
+ def test_multiple_nested_non_dag_withs_do_not_exit_dag_context(self):
+ """Multiple successive non-DAG with-statements inside a DAG block must
not exit the context."""
+ code = """
+from airflow import DAG
+from datetime import datetime
+from airflow.operators.bash import BashOperator
+
+with DAG('my_dag') as dag:
+ with open('a.txt') as f1:
+ pass
+ with open('b.txt') as f2:
+ pass
+ t1 = BashOperator(task_id='t', bash_command=str(datetime.now()))
+"""
+ warnings = self._check_code(code)
+ assert len(warnings) == 1
+
+ def test_task_inside_nested_non_dag_with_is_still_flagged(self):
+ """A task constructed inside a nested non-DAG with-block is still in
DAG context."""
+ code = """
+from airflow import DAG
+from datetime import datetime
+from airflow.operators.bash import BashOperator
+
+with DAG('my_dag') as dag:
+ with open('a.txt') as f:
+ t1 = BashOperator(task_id='t', bash_command=str(datetime.now()))
+"""
+ warnings = self._check_code(code)
+ assert len(warnings) == 1
+
+ def test_task_outside_dag_with_not_flagged_after_nested_fix(self):
+ """Tasks genuinely outside any DAG block must not be flagged — the fix
must not over-flag."""
+ code = """
+from airflow import DAG
+from datetime import datetime
+from airflow.operators.bash import BashOperator
+
+with DAG('my_dag') as dag:
+ with open('a.txt') as f:
+ pass
+
+# This task is outside the DAG with block — should NOT be flagged
+t_outside = BashOperator(task_id='outside', bash_command=str(datetime.now()))
+"""
+ warnings = self._check_code(code)
+ assert len(warnings) == 0, "A task constructed outside the DAG
with-block must not be flagged"