This is an automated email from the ASF dual-hosted git repository.
potiuk 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 40f76fc1080 Add example DAG demonstrating Deadline Alerts (#66269)
40f76fc1080 is described below
commit 40f76fc1080ae67a24fd6d82ecc3960bbe12698a
Author: Jeongseok Kang <[email protected]>
AuthorDate: Sun Aug 30 08:28:35 2026 +0900
Add example DAG demonstrating Deadline Alerts (#66269)
* Add example DAG demonstrating Deadline Alerts
Adds airflow-core/src/airflow/example_dags/example_deadline_alert.py so
users can try the Deadline Alerts feature on a fresh Airflow install
without writing a custom DAG.
Also fixes airflow.sdk's type stub (__init__.pyi) which was missing
AsyncCallback, SyncCallback, DeadlineAlert, and DeadlineReference -
they exist at runtime via lazy imports but the stub had no entries,
causing `from airflow.sdk import DeadlineAlert` (the form used in
docs/howto/deadline-alerts.rst) to fail mypy attr-defined checks.
related: #66268
* Update airflow-core/src/airflow/example_dags/example_deadline_alert.py
Co-authored-by: hojeong park <[email protected]>
* Update airflow-core/src/airflow/example_dags/example_deadline_alert.py
Co-authored-by: hojeong park <[email protected]>
* Allow deadline UUIDs in serialized DAG schema
`SerializedDagModel.write_dag` rewrites `dag.deadline` from a
list of encoded alert dicts to a list of UUIDv7 strings before
persisting, but the JSON schema only permitted dict / list[dict] /
null. Validating any DAG with a deadline against the post-write
shape therefore raised `ValidationError`. Add `list[str]` to the
`deadline` `anyOf` so the schema matches what is actually stored.
* Make example deadline alert DAG actually trip the alert
Address PR review feedback:
- Switch the example from `DAGRUN_LOGICAL_DATE` to `DAGRUN_QUEUED_AT`.
Logical date for a manual run can be far in the past or future, so
`LOGICAL_DATE + interval` rarely produces a meaningful deadline for
a demo DAG. Queued-at is predictable and matches what a reader would
expect when triggering the example.
- Drop the interval from one hour to thirty seconds and sleep for a
minute inside the task so triggering the example DAG actually
observably crosses the deadline and fires the alert.
- Drop the `SyncCallback` stub entry from `airflow.sdk.__init__.pyi`.
The example only uses `AsyncCallback`; `SyncCallback` is mid-rollout
and out of scope for this PR.
* Add regression test for deadline UUID schema acceptance
write_dag rewrites dag.deadline from list[dict] to list[str] (UUIDv7)
before persistence (_generate_deadline_uuids), so the JSON schema's
deadline anyOf must accept list[str]. test_write_dag exposes this
transitively via re-validation of stored data; this commit adds a
focused unit test that guards the contract directly, plus a docstring
cross-reference in _generate_deadline_uuids so the schema coupling is
visible from the rewrite site.
* Add SyncCallback to airflow.sdk type stub
Runtime __init__.py exports SyncCallback via lazy import (alongside
AsyncCallback, DeadlineAlert, DeadlineReference) and the deadline-alerts
documentation imports it directly. Aligning the .pyi stub avoids mypy
attr-defined errors for users following the docs.
* Clarify why example_deadline_alert sleeps synchronously
The 60s sleep is a deliberate choice — a deferred sensor would
demonstrate the same trip path but obscure the DeadlineAlert focus.
Replace the two-line WHAT comment with one line that captures the WHY.
* Make deadline callback context parameter explicit in example DAG
* chore: Remove out-of-scope core changes
* refactor: Remove redundant import aliasing
---------
Co-authored-by: hojeong park <[email protected]>
Co-authored-by: Jarek Potiuk <[email protected]>
---
.../airflow/example_dags/example_deadline_alert.py | 61 ++++++++++++++++++++++
task-sdk/src/airflow/sdk/__init__.pyi | 6 +++
2 files changed, 67 insertions(+)
diff --git a/airflow-core/src/airflow/example_dags/example_deadline_alert.py
b/airflow-core/src/airflow/example_dags/example_deadline_alert.py
new file mode 100644
index 00000000000..54f5f7821bb
--- /dev/null
+++ b/airflow-core/src/airflow/example_dags/example_deadline_alert.py
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Example Dag demonstrating :class:`~airflow.sdk.DeadlineAlert` usage."""
+
+from __future__ import annotations
+
+import logging
+import time
+from datetime import timedelta
+
+import pendulum
+
+# [START example_deadline_alert]
+from airflow.sdk import DAG, AsyncCallback, DeadlineAlert, DeadlineReference,
task
+
+log = logging.getLogger(__name__)
+
+
+async def notify_deadline_missed(context: dict, **kwargs):
+ """Async callback executed by the Triggerer when the deadline is missed."""
+ dag_run = context.get("dag_run")
+ log.warning("Deadline missed for dag_run=%s", dag_run)
+
+
+with DAG(
+ dag_id="example_deadline_alert",
+ description="Demonstrates DeadlineAlert with DAGRUN_QUEUED_AT reference.",
+ schedule=None,
+ start_date=pendulum.datetime(2025, 1, 1, tz="UTC"),
+ catchup=False,
+ tags=["example", "deadline"],
+ deadline=DeadlineAlert(
+ reference=DeadlineReference.DAGRUN_QUEUED_AT,
+ interval=timedelta(seconds=30),
+ callback=AsyncCallback(notify_deadline_missed),
+ name="example_deadline",
+ ),
+) as dag:
+
+ @task
+ def hello_deadline():
+ log.info("Hello from a Dag with a deadline!")
+ # Sleep past the deadline (sync, not a deferred sensor) to keep the
demo focused on DeadlineAlert.
+ time.sleep(60)
+
+ hello_deadline()
+# [END example_deadline_alert]
diff --git a/task-sdk/src/airflow/sdk/__init__.pyi
b/task-sdk/src/airflow/sdk/__init__.pyi
index a86bdeceefd..37585f5d796 100644
--- a/task-sdk/src/airflow/sdk/__init__.pyi
+++ b/task-sdk/src/airflow/sdk/__init__.pyi
@@ -53,6 +53,7 @@ from airflow.sdk.definitions.asset import (
from airflow.sdk.definitions.asset.access_control import AssetAccessControl as
AssetAccessControl
from airflow.sdk.definitions.asset.decorators import asset as asset
from airflow.sdk.definitions.asset.metadata import Metadata as Metadata
+from airflow.sdk.definitions.callback import AsyncCallback, SyncCallback
from airflow.sdk.definitions.connection import Connection as Connection
from airflow.sdk.definitions.context import (
Context as Context,
@@ -60,6 +61,7 @@ from airflow.sdk.definitions.context import (
get_parsing_context as get_parsing_context,
)
from airflow.sdk.definitions.dag import DAG as DAG, dag as dag
+from airflow.sdk.definitions.deadline import DeadlineAlert, DeadlineReference
from airflow.sdk.definitions.decorators import (
result as result,
setup as setup,
@@ -143,6 +145,7 @@ __all__ = [
"AssetAny",
"AssetOrTimeSchedule",
"AssetWatcher",
+ "AsyncCallback",
"BaseAsyncOperator",
"BaseBranchOperator",
"BaseHook",
@@ -161,6 +164,8 @@ __all__ = [
"DAG",
"DagRunState",
"DayWindow",
+ "DeadlineAlert",
+ "DeadlineReference",
"DeltaDataIntervalTimetable",
"DeltaTriggerTimetable",
"EdgeModifier",
@@ -199,6 +204,7 @@ __all__ = [
"StartOfQuarterMapper",
"StartOfWeekMapper",
"StartOfYearMapper",
+ "SyncCallback",
"TaskGroup",
"TaskInstanceState",
"TriggerRule",