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

vatsrahul1001 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 15509f95e53 [v3-3-test] Fix the deadline_reference decorator's 
no-parentheses form (#70708) (#70966)
15509f95e53 is described below

commit 15509f95e5357b73c4b84e96536e909d9d4063fb
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 4 11:08:26 2026 +0530

    [v3-3-test] Fix the deadline_reference decorator's no-parentheses form 
(#70708) (#70966)
    
    * Fix the deadline_reference decorator's no-parenthesis form
    
    Used bare, the decorator silently rebound the decorated class to the inner 
decorator function and skipped registration entirely, surfacing later as an 
unrelated TypeError with an unhelpful message.  Accept both forms, add clearer 
error messaging when a reference cannot be instantiated, and clarify the 
related docs.
    
    * bugfix newsfragment
    
    * sphynx riddles
    (cherry picked from commit 8541668cd4974b7fef9374999209d03ab12a636a)
    
    Co-authored-by: D. Ferruzzi <[email protected]>
---
 airflow-core/docs/howto/deadline-alerts.rst      |  7 ++++
 airflow-core/newsfragments/70708.bugfix.rst      |  1 +
 airflow-core/tests/unit/models/test_deadline.py  | 39 ++++++++++++++++++++
 task-sdk/src/airflow/sdk/definitions/deadline.py | 46 ++++++++++++++++++++----
 4 files changed, 87 insertions(+), 6 deletions(-)

diff --git a/airflow-core/docs/howto/deadline-alerts.rst 
b/airflow-core/docs/howto/deadline-alerts.rst
index df09430862b..ac07047f345 100644
--- a/airflow-core/docs/howto/deadline-alerts.rst
+++ b/airflow-core/docs/howto/deadline-alerts.rst
@@ -420,6 +420,10 @@ references for specific integrations like calendars or 
other data sources. To do
 a class that inherits from BaseDeadlineReference, add the 
``@deadline_reference`` decorator, and
 implement an ``_evaluate_with()`` method.
 
+The decorator may be used with or without parentheses. Used bare, or with 
empty parentheses, the
+reference is evaluated when a new Dag run is created; pass a 
``DeadlineReference.TYPES`` value to
+choose a different time.
+
 
 **Creating a Custom Reference**
 
@@ -522,6 +526,9 @@ followed by a more urgent escalation if the Dag is still 
running.
 **Important Notes:**
 
 * **Timezone Awareness**: Always return timezone-aware datetime objects.
+* **No-argument Construction**: Custom references are instantiated during 
registration, so they must be
+  constructible with no arguments. If your reference takes parameters, 
decorate it with ``@dataclass``
+  and give every field a default value.
 * **Plugin Placement**: One convenient place for custom references is in the 
plugins directory.
 * **API Server Restart**: Restart the Airflow API Server after adding or 
modifying custom references.
 * **Required Parameters**: ``required_kwargs`` declares which Dag run context 
values Airflow should
diff --git a/airflow-core/newsfragments/70708.bugfix.rst 
b/airflow-core/newsfragments/70708.bugfix.rst
new file mode 100644
index 00000000000..16aec527981
--- /dev/null
+++ b/airflow-core/newsfragments/70708.bugfix.rst
@@ -0,0 +1 @@
+The ``@deadline_reference`` decorator can now be used without parentheses. 
Previously, using it that way silently skipped registration and rebound the 
decorated class to the decorator's inner function, which surfaced later as an 
unrelated ``TypeError``.
diff --git a/airflow-core/tests/unit/models/test_deadline.py 
b/airflow-core/tests/unit/models/test_deadline.py
index 99f3d9b127b..d56b9b2eb03 100644
--- a/airflow-core/tests/unit/models/test_deadline.py
+++ b/airflow-core/tests/unit/models/test_deadline.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import re
+from dataclasses import dataclass
 from datetime import datetime, timedelta
 from typing import TYPE_CHECKING
 from unittest import mock
@@ -921,6 +922,44 @@ class TestDeadlineReferenceDecorator:
 
         mock_register.assert_called_once_with(DecoratedCustomRef, timing)
 
+    def test_deadline_reference_decorator_without_parentheses(self):
+        @deadline_reference
+        class BareDecoratedRef(BaseDeadlineReference):
+            def _evaluate_with(self, *, session: Session, **kwargs) -> 
datetime:
+                return timezone.datetime(DEFAULT_DATE)
+
+        # The decorated name must still be the class, not the inner decorator 
function.
+        assert isinstance(BareDecoratedRef, type)
+        assert issubclass(BareDecoratedRef, BaseDeadlineReference)
+
+        assert hasattr(DeadlineReference, BareDecoratedRef.__name__)
+        assert getattr(DeadlineReference, BareDecoratedRef.__name__).__class__ 
is BareDecoratedRef
+
+        assert_correct_timing(BareDecoratedRef, 
DeadlineReference.TYPES.DAGRUN_CREATED)
+        assert_builtin_types_unchanged(
+            DeadlineReference.TYPES.DAGRUN_QUEUED, 
DeadlineReference.TYPES.DAGRUN_CREATED
+        )
+
+    def 
test_deadline_reference_decorator_without_parentheses_invalid_class(self):
+        """Test that the bare form must inherit the base class."""
+        with pytest.raises(ValueError, match="InvalidBareRef must inherit from 
BaseDeadlineReference"):
+
+            @deadline_reference
+            class InvalidBareRef:
+                pass
+
+    def test_deadline_reference_requiring_arguments_raises_helpful_error(self):
+        """Test that a reference which cannot be instantiated with no 
arguments explains itself."""
+        with pytest.raises(TypeError, match="must be constructible with no 
arguments"):
+
+            @deadline_reference()
+            @dataclass
+            class RefWithRequiredField(BaseDeadlineReference):
+                required_field: str
+
+                def _evaluate_with(self, *, session: Session, **kwargs) -> 
datetime:
+                    return timezone.datetime(DEFAULT_DATE)
+
 
 @pytest.mark.db_test
 class TestDeadlineMetricsTeamName:
diff --git a/task-sdk/src/airflow/sdk/definitions/deadline.py 
b/task-sdk/src/airflow/sdk/definitions/deadline.py
index a9da3dd3d3e..ab01a9213aa 100644
--- a/task-sdk/src/airflow/sdk/definitions/deadline.py
+++ b/task-sdk/src/airflow/sdk/definitions/deadline.py
@@ -20,7 +20,7 @@ import logging
 from abc import ABC
 from dataclasses import dataclass
 from datetime import datetime, timedelta
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, overload
 
 import attrs
 
@@ -290,7 +290,15 @@ class DeadlineReference:
             raise ValueError(f"{reference_class.__name__} must inherit from 
BaseDeadlineReference")
 
         # Register the new reference with DeadlineReference for discoverability
-        setattr(cls, reference_class.__name__, reference_class())
+        try:
+            reference_instance = reference_class()
+        except TypeError as e:
+            raise TypeError(
+                f"{reference_class.__name__} must be constructible with no 
arguments in order to be "
+                f"registered as a deadline reference. If it takes parameters, 
decorate it with "
+                f"@dataclass and give every field a default value. Original 
error: {e}"
+            ) from e
+        setattr(cls, reference_class.__name__, reference_instance)
         logger.info("Registered DeadlineReference %s", 
reference_class.__name__)
 
         # Add to appropriate deadline_reference_type classification
@@ -310,34 +318,60 @@ class DeadlineReference:
         return reference_class
 
 
+@overload
+def deadline_reference(
+    deadline_reference_type: type[BaseDeadlineReference],
+) -> type[BaseDeadlineReference]: ...
+
+
+@overload
 def deadline_reference(
     deadline_reference_type: DeadlineReferenceTypes | None = None,
-) -> Callable[[type[BaseDeadlineReference]], type[BaseDeadlineReference]]:
+) -> Callable[[type[BaseDeadlineReference]], type[BaseDeadlineReference]]: ...
+
+
+def deadline_reference(deadline_reference_type=None):
     """
     Decorate a class to register a custom deadline reference.
 
-    Usage:
+    May be used with or without parentheses. Without parentheses the reference 
is evaluated when a
+    new dagrun is created; pass a ``DeadlineReference.TYPES`` value to choose 
a different time.
+
+    .. code-block:: python
+
+        @deadline_reference
+        class MyBareReference(BaseDeadlineReference):
+            # Equivalent to @deadline_reference(); evaluated when a new dagrun 
is created.
+            def _evaluate_with(self, *, session: Session, **kwargs) -> 
datetime:
+                return some_datetime
+
+
         @deadline_reference()
         class MyCustomReference(BaseDeadlineReference):
             # By default, evaluate_with will be called when a new dagrun is 
created.
             def _evaluate_with(self, *, session: Session, **kwargs) -> 
datetime:
                 # Put your business logic here (use deferred imports for Core 
types)
                 from airflow.models import DagRun
+
                 return some_datetime
 
             def serialize_reference(self) -> dict:
                 return {"reference_type": self.reference_name}
 
+
+        # Optionally, specify when it is calculated by providing a 
DeadlineReference.TYPES value.
         @deadline_reference(DeadlineReference.TYPES.DAGRUN_QUEUED)
         class MyQueuedRef(BaseDeadlineReference):
-            # Optionally, you can specify when you want it calculated by 
providing a DeadlineReference.TYPES
             def _evaluate_with(self, *, session: Session, **kwargs) -> 
datetime:
-                 # Put your business logic here
+                # Put your business logic here
                 return some_datetime
 
             def serialize_reference(self) -> dict:
                 return {"reference_type": self.reference_name}
     """
+    # Used bare, without parentheses: the decorated class is passed in 
directly.
+    if isinstance(deadline_reference_type, type):
+        return 
DeadlineReference.register_custom_reference(deadline_reference_type)
 
     def decorator(
         reference_class: type[BaseDeadlineReference],

Reply via email to