amoghrajesh commented on code in PR #70370:
URL: https://github.com/apache/airflow/pull/70370#discussion_r4070061048


##########
airflow-core/src/airflow/serialization/decoders.py:
##########
@@ -338,6 +341,43 @@ def decode_deadline_alert(encoded_data: dict):
     )
 
 
+def decode_deadline_alert_model(deadline_alert: DeadlineAlertModel) -> 
SerializedDeadlineAlert:
+    """
+    Decode a ``DeadlineAlert`` ORM row into its serialized representation.
+
+    :meta private:
+    """
+    return decode_deadline_alert(
+        {
+            DeadlineAlertFields.REFERENCE: deadline_alert.reference,
+            DeadlineAlertFields.INTERVAL: deadline_alert.interval,
+            DeadlineAlertFields.CALLBACK: deadline_alert.callback_def,

Review Comment:
   This helper is named as a general "decode a DeadlineAlert ORM row", but it 
skips the row's `name`. The model has a `name` column and 
`decode_deadline_alert` reads it (line 337 just above), so 
`SerializedDeadlineAlert.name` comes back as None even when the row has one.
   
   Nothing reads `.name` on either of your two callers, so this isn't a bug as 
of today but its a trap for the next caller. Worth adding:
   ```
       DeadlineAlertFields.NAME: deadline_alert.name,
   ```



##########
airflow-core/tests/unit/models/test_taskinstance.py:
##########
@@ -4130,8 +4130,28 @@ async def empty_callback_for_deadline():
     pass
 
 
-def test_clear_task_instances_recalculates_dagrun_queued_deadlines(dag_maker, 
session):
-    """Test that clearing tasks recalculates all (and only) DAGRUN_QUEUED_AT 
deadlines."""
[email protected](
+    "use_variable_interval",
+    [
+        pytest.param(False, id="fixed_timedelta_interval"),
+        pytest.param(True, id="variable_interval"),
+    ],
+)
+def test_clear_task_instances_recalculates_dagrun_queued_deadlines(dag_maker, 
session, use_variable_interval):
+    """Test that clearing tasks recalculates all (and only) DAGRUN_QUEUED_AT 
deadlines.
+
+    Since Airflow 3.3 the ``deadline_alert.interval`` column is JSON (a 
serialized ``timedelta``
+    or ``VariableInterval``), so the recalculation must decode it instead of 
passing the raw value
+    to ``timedelta()``. Storing the interval via ``serialize`` here mirrors 
production and covers
+    both interval kinds.
+    """
+    from airflow.models.variable import Variable
+    from airflow.sdk.definitions.deadline import VariableInterval
+    from airflow.sdk.serde import serialize

Review Comment:
   Top level import pls.



##########
airflow-core/src/airflow/serialization/decoders.py:
##########
@@ -338,6 +341,43 @@ def decode_deadline_alert(encoded_data: dict):
     )
 
 
+def decode_deadline_alert_model(deadline_alert: DeadlineAlertModel) -> 
SerializedDeadlineAlert:
+    """
+    Decode a ``DeadlineAlert`` ORM row into its serialized representation.
+
+    :meta private:
+    """
+    return decode_deadline_alert(
+        {
+            DeadlineAlertFields.REFERENCE: deadline_alert.reference,
+            DeadlineAlertFields.INTERVAL: deadline_alert.interval,
+            DeadlineAlertFields.CALLBACK: deadline_alert.callback_def,
+        }
+    )
+
+
+def resolve_deadline_alert_interval(
+    alert: SerializedDeadlineAlert, *, session: Session | None = None
+) -> datetime.timedelta:
+    """
+    Resolve a decoded alert's interval to a ``timedelta``.
+
+    A ``SerializedVariableInterval`` reads its Airflow Variable here, so this 
is only called at
+    the point a deadline is actually calculated. It raises ``ValueError`` if 
the Variable is
+    missing or is not an integer number of seconds.
+
+    :param alert: The decoded alert whose interval should be resolved.
+    :param session: Existing SQLAlchemy Session. Both callers run under the 
scheduler's
+        ``prohibit_commit`` guard, so the open session has to reach 
``Variable.get`` instead of
+        ``provide_session`` handing back the same scoped session and rolling 
it back on exit.

Review Comment:
   Small thing: this session rationale is written out four times - here, and 
again in test_dagrun.py:1568, test_taskinstance.py:4248, and 
test_decoders.py:41. Keep it here as the source of truth and let the tests 
point at it



##########
airflow-core/tests/unit/models/test_taskinstance.py:
##########
@@ -4205,13 +4263,88 @@ def 
test_clear_task_instances_recalculates_dagrun_queued_deadlines(dag_maker, se
     for deadline in deadlines_after:
         if deadline.deadline_time != 
deadline_times_by_alert[deadline.deadline_alert_id]:
             recalculated_count += 1
-            deadline_alert = session.get(DeadlineAlertModel, 
deadline.deadline_alert_id)
-            expected_time = dag_run.queued_at + 
datetime.timedelta(seconds=deadline_alert.interval)
+            expected_time = dag_run.queued_at + 
expected_resolved_by_alert[deadline.deadline_alert_id]
             assert deadline.deadline_time == expected_time
 
     assert recalculated_count == 2
 
 
+def 
test_clear_task_instances_skips_deadline_with_unresolvable_interval(dag_maker, 
session):
+    """A variable-backed interval that cannot be resolved must not abort the 
clear.
+
+    ``SerializedVariableInterval.resolve()`` raises ``ValueError`` when the 
Airflow Variable is
+    missing or is not an integer, and that happens while the DAG run is being 
cleared. The clear
+    should still go through, leaving the unresolvable deadline at its old time.
+    """
+    from airflow.models.variable import Variable
+    from airflow.sdk.definitions.deadline import VariableInterval
+    from airflow.sdk.serde import serialize

Review Comment:
   Top level import pls.



##########
airflow-core/src/airflow/serialization/decoders.py:
##########
@@ -338,6 +341,43 @@ def decode_deadline_alert(encoded_data: dict):
     )
 
 
+def decode_deadline_alert_model(deadline_alert: DeadlineAlertModel) -> 
SerializedDeadlineAlert:
+    """
+    Decode a ``DeadlineAlert`` ORM row into its serialized representation.
+
+    :meta private:
+    """
+    return decode_deadline_alert(
+        {
+            DeadlineAlertFields.REFERENCE: deadline_alert.reference,
+            DeadlineAlertFields.INTERVAL: deadline_alert.interval,
+            DeadlineAlertFields.CALLBACK: deadline_alert.callback_def,
+        }
+    )
+
+
+def resolve_deadline_alert_interval(
+    alert: SerializedDeadlineAlert, *, session: Session | None = None

Review Comment:
   The signature makes `session` optional, but the docstring right below spends 
three lines explaining that a caller who omits it gets its session rolled back 
under `prohibit_commit`. Both callers bypass it.
   
   Can this be `*, session: Session` with no default? That turns a silent 
misuse into a TypeError at the caller, which is the whole point of the three 
tests you added for this.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to