You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ra...@apache.org on 2026/08/04 05:38:36 UTC
(airflow) branch v3-3-test updated: [v3-3-test] Fix the deadline_reference decorator's no-parentheses form (#70708) (#70966)
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] <41...@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 <fe...@amazon.com>
---
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],