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

vatsrahul1001 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 0e516c37571 Improve deadline diagnostics for null DagRun fields 
(#71767)
0e516c37571 is described below

commit 0e516c37571562919983e6faa9ca748e7fad3ae0
Author: Vincent Hsiao <[email protected]>
AuthorDate: Wed Sep 9 22:39:02 2026 +0800

    Improve deadline diagnostics for null DagRun fields (#71767)
    
    * Improve deadline diagnostics for null DagRun fields
    
    * Limit null deadline warnings to DagRun timestamp references
    
    * Add bugfix newsfragment for deadline diagnostics
    
    * Fix deadline diagnostics newsfragment formatting
    
    * Fix deadline diagnostics for null DagRun timestamps
---
 airflow-core/newsfragments/71767.bugfix.rst        |  1 +
 .../src/airflow/serialization/definitions/dag.py   | 16 ++++++
 .../airflow/serialization/definitions/deadline.py  |  9 ++--
 airflow-core/tests/unit/models/test_dagrun.py      | 58 ++++++++++++++++++++++
 airflow-core/tests/unit/models/test_deadline.py    | 35 ++++++++++++-
 5 files changed, 115 insertions(+), 4 deletions(-)

diff --git a/airflow-core/newsfragments/71767.bugfix.rst 
b/airflow-core/newsfragments/71767.bugfix.rst
new file mode 100644
index 00000000000..18c2de4a84a
--- /dev/null
+++ b/airflow-core/newsfragments/71767.bugfix.rst
@@ -0,0 +1 @@
+Fix misleading deadline warnings when DagRun-based deadline references 
evaluate to None because the referenced timestamp field is null.
diff --git a/airflow-core/src/airflow/serialization/definitions/dag.py 
b/airflow-core/src/airflow/serialization/definitions/dag.py
index 5b3a5352430..5d5c4763622 100644
--- a/airflow-core/src/airflow/serialization/definitions/dag.py
+++ b/airflow-core/src/airflow/serialization/definitions/dag.py
@@ -83,6 +83,11 @@ if TYPE_CHECKING:
 
 log = structlog.get_logger(__name__)
 
+_DAGRUN_REFERENCE_REQUIRED_COLUMNS = {
+    SerializedReferenceModels.DagRunLogicalDateDeadline: "logical_date",
+    SerializedReferenceModels.DagRunQueuedAtDeadline: "queued_at",
+}
+
 
 # TODO (GH-52141): Share definition with SDK?
 class EdgeInfoType(TypedDict):
@@ -789,6 +794,17 @@ class SerializedDAG:
                         "deadline_alerts.deadline_created",
                         tags=prune_dict({"dag_id": self.dag_id, "team_name": 
team_name}),
                     )
+                elif required_dagrun_column := 
_DAGRUN_REFERENCE_REQUIRED_COLUMNS.get(
+                    type(deserialized_deadline_alert.reference)
+                ):
+                    log.warning(
+                        "skipping deadline alert because the deadline 
reference evaluated to None",
+                        dag_id=self.dag_id,
+                        run_id=orm_dagrun.run_id,
+                        deadline_alert_id=deadline_alert.id,
+                        
reference_type=deserialized_deadline_alert.reference.reference_name,
+                        required_dagrun_column=required_dagrun_column,
+                    )
 
     @provide_session
     def set_task_instance_state(
diff --git a/airflow-core/src/airflow/serialization/definitions/deadline.py 
b/airflow-core/src/airflow/serialization/definitions/deadline.py
index e5108c6267c..5f462c63d1a 100644
--- a/airflow-core/src/airflow/serialization/definitions/deadline.py
+++ b/airflow-core/src/airflow/serialization/definitions/deadline.py
@@ -373,10 +373,13 @@ def _fetch_from_db(column, *, session: Session, dag_id: 
str, run_id: str) -> dat
     """
     from airflow.models import DagRun
 
-    result = session.execute(select(column).where(DagRun.dag_id == dag_id, 
DagRun.run_id == run_id)).scalar()
-    if result is None:
+    row = session.execute(
+        select(column).where(DagRun.dag_id == dag_id, DagRun.run_id == run_id)
+    ).one_or_none()
+    if row is None:
         logger.warning("Could not find DagRun for dag_id=%s, run_id=%s", 
dag_id, run_id)
-    return result
+        return None
+    return row[0]
 
 
 @attrs.define(frozen=True)
diff --git a/airflow-core/tests/unit/models/test_dagrun.py 
b/airflow-core/tests/unit/models/test_dagrun.py
index fbcb9cb8cef..0a3250c3c62 100644
--- a/airflow-core/tests/unit/models/test_dagrun.py
+++ b/airflow-core/tests/unit/models/test_dagrun.py
@@ -1546,6 +1546,64 @@ class TestDagRun:
         deadline = session.execute(select(Deadline)).scalars().one_or_none()
         assert deadline.deadline_time == first_deadline_time
 
+    def test_dagrun_deadline_logs_when_reference_column_is_null(self, session, 
deadline_test_dag, caplog):
+        scheduler_dag = deadline_test_dag(
+            deadline=DeadlineAlert(
+                reference=DeadlineReference.DAGRUN_LOGICAL_DATE,
+                interval=datetime.timedelta(minutes=5),
+                callback=AsyncCallback(empty_callback_for_deadline),
+            ),
+        )
+
+        with caplog.at_level("WARNING"):
+            scheduler_dag.create_dagrun(
+                run_id="manual__null_logical_date",
+                run_type=DagRunType.MANUAL,
+                logical_date=None,
+                data_interval=None,
+                run_after=timezone.utcnow(),
+                start_date=timezone.utcnow(),
+                state=DagRunState.QUEUED,
+                triggered_by=DagRunTriggeredByType.TEST,
+                session=session,
+            )
+
+        assert session.execute(select(Deadline)).scalars().one_or_none() is 
None
+        assert {
+            "event": "skipping deadline alert because the deadline reference 
evaluated to None",
+            "dag_id": "test_dag",
+            "run_id": "manual__null_logical_date",
+            "reference_type": "DagRunLogicalDateDeadline",
+            "required_dagrun_column": "logical_date",
+            "log_level": "warning",
+        } in caplog
+        assert not any("Could not find DagRun" in record.message for record in 
caplog.records)
+
+    def test_dagrun_deadline_does_not_warn_for_average_runtime_without_history(
+        self, session, deadline_test_dag, caplog
+    ):
+        scheduler_dag = deadline_test_dag(
+            deadline=DeadlineAlert(
+                reference=DeadlineReference.AVERAGE_RUNTIME(max_runs=10, 
min_runs=5),
+                interval=datetime.timedelta(minutes=5),
+                callback=AsyncCallback(empty_callback_for_deadline),
+            ),
+        )
+
+        with caplog.at_level("WARNING", 
logger="airflow.serialization.definitions.dag"):
+            self.create_dag_run(
+                dag=scheduler_dag,
+                logical_date=DEFAULT_DATE,
+                session=session,
+            )
+
+        assert session.execute(select(Deadline)).scalars().one_or_none() is 
None
+        assert {
+            "event": "skipping deadline alert because the deadline reference 
evaluated to None",
+            "reference_type": "AverageRuntimeDeadline",
+            "log_level": "warning",
+        } not in caplog
+
     @mock.patch.object(Deadline, "prune_deadlines")
     def test_dagrun_deadline_variable_interval_missing_variable_fails(self, _, 
session, deadline_test_dag):
 
diff --git a/airflow-core/tests/unit/models/test_deadline.py 
b/airflow-core/tests/unit/models/test_deadline.py
index 1771269b548..87c383d3e9c 100644
--- a/airflow-core/tests/unit/models/test_deadline.py
+++ b/airflow-core/tests/unit/models/test_deadline.py
@@ -42,7 +42,10 @@ from airflow.sdk.definitions.deadline import (
     FixedDatetimeDeadline,
     deadline_reference,
 )
-from airflow.serialization.definitions.deadline import 
SerializedReferenceModels
+from airflow.serialization.definitions.deadline import (
+    SerializedReferenceModels,
+    _fetch_from_db as _fetch_serialized_deadline_from_db,
+)
 from airflow.utils.state import DagRunState
 
 from tests_common.test_utils import db
@@ -468,6 +471,36 @@ class TestCalculatedDeadlineDatabaseCalls:
                 mock_fetch.assert_not_called()
                 assert result == DEFAULT_DATE + interval
 
+    def 
test_serialized_fetch_from_db_returns_null_column_without_missing_dagrun_warning(
+        self, session, dag_maker, caplog
+    ):
+        with dag_maker(DAG_ID):
+            EmptyOperator(task_id="test_task")
+
+        dag_maker.create_dagrun(
+            logical_date=None,
+            run_id="manual__null_logical_date",
+            state=DagRunState.QUEUED,
+        )
+        session.commit()
+
+        with caplog.at_level("WARNING", 
logger="airflow.serialization.definitions.deadline"):
+            result = _fetch_serialized_deadline_from_db(
+                DagRun.logical_date, session=session, dag_id=DAG_ID, 
run_id="manual__null_logical_date"
+            )
+
+        assert result is None
+        assert not any("Could not find DagRun" in record.message for record in 
caplog.records)
+
+    def test_serialized_fetch_from_db_logs_missing_dagrun(self, session, 
caplog):
+        with caplog.at_level("WARNING", 
logger="airflow.serialization.definitions.deadline"):
+            result = _fetch_serialized_deadline_from_db(
+                DagRun.logical_date, session=session, dag_id=DAG_ID, 
run_id="missing_run"
+            )
+
+        assert result is None
+        assert any("Could not find DagRun" in record.message for record in 
caplog.records)
+
     def test_average_runtime_with_sufficient_history(self, session, dag_maker):
         """Test AverageRuntimeDeadline when enough historical data exists."""
         with dag_maker(DAG_ID):

Reply via email to