This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 62e3b1a1dce [v3-4-test] Stop a commented executable_resumable from 
marking a module durable (#73607) (#75660)
62e3b1a1dce is described below

commit 62e3b1a1dce0cd68fdc8ee250630967e2b0a98f7
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Oct 26 14:46:07 2026 +0000

    [v3-4-test] Stop a commented executable_resumable from marking a module 
durable (#73607) (#75660)
    
    (cherry picked from commit 77da6dac565e9e8968b446a54f9c3734130b6f90)
    
    Co-authored-by: Amogh Desai <[email protected]>
---
 dev/registry/extract_parameters.py                 | 12 ++-
 dev/registry/tests/test_extract_parameters.py      | 97 ++++++++++++++++++++++
 .../tests/test_registry_contract_models.py         | 10 ++-
 3 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/dev/registry/extract_parameters.py 
b/dev/registry/extract_parameters.py
index df9f344ab00..4536a5a48e9 100644
--- a/dev/registry/extract_parameters.py
+++ b/dev/registry/extract_parameters.py
@@ -529,7 +529,9 @@ def _execute_chain_calls_resumable(cls: type, depth: int) 
-> bool:
     """Return True if some class along `cls`'s resolved `execute()` chain 
calls execute_resumable().
 
     Same walk as `_delegates_execute_to`: a delegating override's own source 
may not mention
-    `execute_resumable` even though the class it hands off to does.
+    `execute_resumable` even though the class it hands off to does. Only a 
real call counts,
+    so the match is made on the parsed tree: a comment, docstring or other 
string literal
+    naming the call is not the call.
     """
     owner = _find_owner_of_execute(cls.__mro__, 0)
     remaining = depth
@@ -537,8 +539,14 @@ def _execute_chain_calls_resumable(cls: type, depth: int) 
-> bool:
         source = _get_method_source(owner, "execute")
         if source is None:
             return False
-        if "execute_resumable" in source:
+        if any(
+            isinstance(node, ast.Call)
+            and isinstance(node.func, ast.Attribute)
+            and node.func.attr == "execute_resumable"
+            for node in ast.walk(ast.parse(textwrap.dedent(source)))
+        ):
             return True
+        source = _strip_comment_lines(source)
         if remaining <= 0:
             return False
         owner = _next_execute_hop(cls, owner, source)
diff --git a/dev/registry/tests/test_extract_parameters.py 
b/dev/registry/tests/test_extract_parameters.py
index b751ae64f08..b46bd7aafe1 100644
--- a/dev/registry/tests/test_extract_parameters.py
+++ b/dev/registry/tests/test_extract_parameters.py
@@ -383,6 +383,27 @@ class TestIsDurableCapable:
 
         assert is_durable_capable(CommentedNonDelegatingSubclass, 
FakeResumableJobMixin) is False
 
+    def test_mentioning_execute_resumable_is_not_mistaken_for_a_call(self):
+        """Only a real `execute_resumable()` call counts: a whole-line 
comment, a trailing
+        comment or a docstring naming it would otherwise report the subclass 
capable."""
+
+        class WholeLineComment(FullyImplementedResumableOperator):
+            def execute(self, context):
+                # this path deliberately skips execute_resumable(context)
+                return None
+
+        class TrailingComment(FullyImplementedResumableOperator):
+            def execute(self, context):
+                return None  # deliberately skips execute_resumable(context)
+
+        class Docstring(FullyImplementedResumableOperator):
+            def execute(self, context):
+                """Deliberately skips execute_resumable()."""
+                return None
+
+        for operator_cls in (WholeLineComment, TrailingComment, Docstring):
+            assert is_durable_capable(operator_cls, FakeResumableJobMixin) is 
False, operator_cls.__name__
+
     def test_non_class_execute_call_does_not_shadow_real_delegation(self):
         """A local name that is not a class in the MRO (e.g. a DB cursor) is 
skipped rather
         than dead-ending the walk before the real delegation below it."""
@@ -619,6 +640,70 @@ def _wrap_in_foreign_module(func):
     return foreign.__dict__["wrapper"]
 
 
+class GrandparentCallingOverridableHelper:
+    """Its execute() dispatches to a helper a subclass is free to replace."""
+
+    def execute(self, context):
+        return self.run_job(context)
+
+    def run_job(self, context):
+        return None
+
+
+class MiddleDelegatingToSuperExecute(GrandparentCallingOverridableHelper):
+    def execute(self, context):
+        return super().execute(context)
+
+
+class SubclassOverridingHelperToDefer(MiddleDelegatingToSuperExecute):
+    """Only the subclass's copy of `run_job` defers, and the call to it lives 
two hops up
+    the chain. Resolving `self.<name>()` against the class the hop landed on 
instead of the
+    class being asked about finds the grandparent's inert copy and reports not 
deferrable.
+    """
+
+    def run_job(self, context):
+        return self.defer()
+
+    def defer(self, *args, **kwargs):
+        return None
+
+
+class SharedHelperReachedAtTwoBudgets:
+    """`check_state` is reached twice: once down the long leg with too little 
depth budget
+    left to walk out to the defer, and once straight from execute() with 
budget to spare.
+    A `visited` key without the remaining depth in it skips the second visit 
and reports
+    not deferrable.
+    """
+
+    def execute(self, context):
+        self.first_leg(context)
+        return self.check_state(context)
+
+    def first_leg(self, context):
+        return self.second_leg(context)
+
+    def second_leg(self, context):
+        return self.third_leg(context)
+
+    def third_leg(self, context):
+        return self.check_state(context)
+
+    def check_state(self, context):
+        return self.poll(context)
+
+    def poll(self, context):
+        return self.wait_for_job(context)
+
+    def wait_for_job(self, context):
+        return self.submit(context)
+
+    def submit(self, context):
+        return self.defer()
+
+    def defer(self, *args, **kwargs):
+        return None
+
+
 class DeferralCommentOnlyOperator:
     """A comment naming self.defer() is not a deferral, the same way it is not 
delegation."""
 
@@ -707,6 +792,18 @@ class TestSupportsDeferrable:
     def test_comment_mentioning_defer_is_not_mistaken_for_deferral(self):
         assert supports_deferrable(DeferralCommentOnlyOperator) is False
 
+    @pytest.mark.parametrize(
+        "operator_cls",
+        [
+            pytest.param(SubclassOverridingHelperToDefer, 
id="helper-resolved-against-class-under-test"),
+            pytest.param(SharedHelperReachedAtTwoBudgets, 
id="helper-rewalked-with-more-depth-budget"),
+        ],
+    )
+    # SharedHelperReachedAtTwoBudgets only exercises the depth-aware `visited` 
key at this budget.
+    @patch("extract_parameters._MAX_DEFERRAL_WALK_DEPTH", 6)
+    def test_helper_call_is_walked_to_the_deferral(self, operator_cls):
+        assert supports_deferrable(operator_cls) is True
+
 
 # ---------------------------------------------------------------------------
 # Module dataclass
diff --git a/dev/registry/tests/test_registry_contract_models.py 
b/dev/registry/tests/test_registry_contract_models.py
index 0e29b7aa2c4..d5bd9d8f2ec 100644
--- a/dev/registry/tests/test_registry_contract_models.py
+++ b/dev/registry/tests/test_registry_contract_models.py
@@ -99,8 +99,9 @@ def 
test_module_contract_accepts_legacy_modules_without_supports_durable_executi
 
 
 def test_module_contract_preserves_supports_durable_execution_true():
-    validated = validate_modules_catalog({"modules": 
[_module_payload(supports_durable_execution=True)]})
-    assert validated["modules"][0]["supports_durable_execution"] is True
+    payload = _module_payload(supports_durable_execution=True)
+    validate_modules_catalog({"modules": [payload]})
+    assert ModuleContract.model_validate(payload).supports_durable_execution 
is True
 
 
 def test_module_contract_accepts_legacy_modules_without_supports_deferrable():
@@ -110,8 +111,9 @@ def 
test_module_contract_accepts_legacy_modules_without_supports_deferrable():
 
 
 def test_module_contract_preserves_supports_deferrable_true():
-    validated = validate_modules_catalog({"modules": 
[_module_payload(supports_deferrable=True)]})
-    assert validated["modules"][0]["supports_deferrable"] is True
+    payload = _module_payload(supports_deferrable=True)
+    validate_modules_catalog({"modules": [payload]})
+    assert ModuleContract.model_validate(payload).supports_deferrable is True
 
 
 def test_module_contract_omits_guide_url_for_undocumented_classes():

Reply via email to