This is an automated email from the ASF dual-hosted git repository.
ashb 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 b8ef78f53ad Fix misleading timetable Variable example in best
practices (#73945)
b8ef78f53ad is described below
commit b8ef78f53adfbe3ff13ff18debc52e6e00ddbe52
Author: Adam Postek <[email protected]>
AuthorDate: Fri Oct 9 13:23:34 2026 +0000
Fix misleading timetable Variable example in best practices (#73945)
* Fix misleading timetable Variable example in best practices
* Explain how to use Variables with custom timetables
---
airflow-core/docs/best-practices.rst | 52 +++++++++++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 4 deletions(-)
diff --git a/airflow-core/docs/best-practices.rst
b/airflow-core/docs/best-practices.rst
index 3f8cdaaf4d9..495c739b01e 100644
--- a/airflow-core/docs/best-practices.rst
+++ b/airflow-core/docs/best-practices.rst
@@ -463,9 +463,18 @@ for any variable that contains sensitive data.
Timetables
----------
-Avoid using Airflow Variables/Connections or accessing Airflow database at the
top level of your timetable code.
-Database access should be delayed until the execution time of the Dag. This
means that you should not have variables/connections retrieval
-as argument to your timetable class initialization or have Variable/connection
at the top level of your custom timetable module.
+Avoid using Airflow Variables/Connections or accessing Airflow database
anywhere in your timetable code: at the
+top level of the module, in ``__init__``, and in scheduling methods such as
``next_dagrun_info``. The Dag
+processor and the scheduler rebuild your timetable from the serialized Dag by
calling ``deserialize``, which
+calls ``__init__`` again, so this code runs every time they load the Dag, not
only when the Dag file is parsed.
+Each load queries the Variable again, and if the lookup fails, for example
because the Variable does not exist,
+the timetable cannot be loaded.
+
+Instead, pass configuration as plain arguments to your timetable and store
them with ``serialize`` and
+``deserialize``, as described in :doc:`/howto/timetable`. If the value comes
from a Variable, read it in the
+Dag file and pass it in: it is then serialized with the timetable and
refreshed whenever the Dag file is
+parsed, and the scheduler never looks it up. Reading Variables in the Dag file
has its own cost, see
+:ref:`best_practices/airflow_variables`.
Bad example:
@@ -480,7 +489,7 @@ Bad example:
self._something = something
super().__init__(*args, **kwargs)
-Good example:
+Also a bad example, because ``__init__`` runs again when the scheduler
deserializes the timetable:
.. code-block:: python
@@ -493,6 +502,41 @@ Good example:
self._something = Variable.get(something)
super().__init__(*args, **kwargs)
+Good example:
+
+.. code-block:: python
+
+ from typing import Any
+
+ from airflow.timetables.interval import CronDataIntervalTimetable
+
+
+ class CustomTimetable(CronDataIntervalTimetable):
+ def __init__(self, *args, something="something", **kwargs):
+ self._something = something
+ super().__init__(*args, **kwargs)
+
+ def serialize(self) -> dict[str, Any]:
+ return {**super().serialize(), "something": self._something}
+
+ @classmethod
+ def deserialize(cls, data: dict[str, Any]) -> "CustomTimetable":
+ timetable = super().deserialize(data)
+ timetable._something = data["something"]
+ return timetable
+
+and in the Dag file:
+
+.. code-block:: python
+
+ from airflow.sdk import DAG, Variable
+
+ with DAG(
+ dag_id="my_dag",
+ schedule=CustomTimetable("0 0 * * *", timezone="UTC",
something=Variable.get("something")),
+ ):
+ ...
+
Triggering Dags after changes
-----------------------------