Uh oh!
There was an error while loading. Please reload this page.
Fix TypeError clearing a DAG run when a queued-at deadline interval is stored as JSON - #70370
Fix TypeError clearing a DAG run when a queued-at deadline interval is stored as JSON#70370hkc-8010 wants to merge 2 commits into
Conversation
Uh oh!
There was an error while loading. Please reload this page.
amoghrajesh
left a comment
There was a problem hiding this comment.
This propagates the same coupling I raised in #64751 - isinstance(interval, VariableInterval) here imports airflow.sdk.definitions.deadline.VariableInterval into core airflow at runtime. See #64751 (review) for details.
…s stored as JSON Since 3.3.0 the deadline_alert.interval column is JSON (migration 0117, apache#64751), holding a serialized timedelta or VariableInterval. _recalculate_dagrun_queued_at_deadlines still passed that raw dict into timedelta(seconds=...), so clearing a DAG run or task instance whose DAG has a DagRunQueuedAtDeadline failed with "TypeError: unsupported type for timedelta seconds component: dict" (HTTP 500). Decode the interval the same way DagRun creation does (decode_deadline_alert), then resolve a VariableInterval to a timedelta before recalculating. The existing test stored the interval as a plain float and never exercised the JSON path; it now stores the serialized envelope and is parametrized over fixed-timedelta and VariableInterval. closes: apache#70368
Move the decode and interval resolution into decode_deadline_alert_model() and resolve_deadline_alert_interval() in serialization/decoders.py, so the deadline recalculation on clear does not need its own copy or an airflow.sdk import. SerializedDAG._process_dagrun_deadline_alerts was doing the same two steps inline and now calls the helpers, dropping its VariableInterval import. closes: apache#70368
d906351 to
92c00e3Comparehkc-8010
commented
Aug 23, 2026
@amoghrajesh Agreed the counterpart is the real fix, and #71802 is already adding The duplication you flagged is gone too. |
ferruzzi
left a comment
There was a problem hiding this comment.
@amoghrajesh looks like the SDK coupling you flagged has been addressed, and I think this is clear to move now if you agree.
models/taskinstance.py no longer imports from the SDK at all, and the single remaining isinstance(..., VariableInterval) is confined to resolve_deadline_alert_interval() in serialization/decoders.py. Worth noting that decoders.py already imported VariableInterval (function-locally, inside decode_deadline_alert), so this consolidates an existing coupling site rather than adding one, and serialization/definitions/dag.py loses its own import in the process. The core to SDK coupling is one file lower than before the PR.
#71802 then replaces that last isinstance in one place, so nothing here has to move again.
This PR is the only one of the three currently touching _recalculate_dagrun_queued_at_deadlines that is milestoned and backport labelled, so it is the only route this fix has to 3.3.x. More context in my comments on #70714 and #71850.
| :meta private: | ||
| """ | ||
| if isinstance(alert.interval, VariableInterval): | ||
| return alert.interval.resolve() |
There was a problem hiding this comment.
resolve() can raise ValueError (for example VariableInterval.resolve() raises on a non-integer), so on the clear path this can abort the dagrun clear even though there is a valid deadline in the db. Since dagrun create and dagrun clear both filter through here now, I think we should be catching and logging in the taskinstance.py where this gets called IF we're clearing.
| import dateutil.relativedelta | ||
| from airflow._shared.module_loading import import_string | ||
| from airflow.sdk.definitions.deadline import VariableInterval |
There was a problem hiding this comment.
I'm not sure about this one. We moved this sdk import to the module level but left the other sdk import below it as a local?
Summary
Clearing a DAG run or task instance whose DAG defines a deadline referencing the run's queued-at time (
DeadlineReference.DAGRUN_QUEUED_AT) raisedTypeError: unsupported type for timedelta seconds component: dictand surfaced as an HTTP 500 in the API server.Since 3.3.0 the
deadline_alert.intervalcolumn is JSON (migration0117_3_3_0_change_deadline_interval_to_json.py, from #64751), holding a serializedtimedeltaorVariableInterval, e.g.{"__classname__": "datetime.timedelta", "__version__": 2, "__data__": 3600.0}._recalculate_dagrun_queued_at_deadlines(called fromclear_task_instances) still passed that raw dict intotimedelta(seconds=...). Every other consumer (DagRun creation inserialization/definitions/dag.py, the model__repr__) decodes the JSON first; only the clear path was missed.DagRun creation already had the decode plus interval resolution written out inline, so rather than copy it, both steps move into
serialization/decoders.pyand both call sites use them.Changes
airflow-core/src/airflow/serialization/decoders.py: adddecode_deadline_alert_model()(decode aDeadlineAlertORM row) andresolve_deadline_alert_interval()(resolve aVariableIntervalto atimedelta).VariableIntervalmoves to a module-level import so the two functions share it, anddecode_deadline_alertgets its missing return annotation.airflow-core/src/airflow/models/taskinstance.py:_recalculate_dagrun_queued_at_deadlinescalls the two helpers instead of building the encoded dict itself. Noairflow.sdkimport.airflow-core/src/airflow/serialization/definitions/dag.py:_process_dagrun_deadline_alertswas doing the same two steps inline and now calls the helpers, so itsVariableInterval,DeadlineAlertFields,DATandEncodingimports are gone.generated/known_sdk_imports_in_core.txt: the hook tighteneddefinitions/dag.pyfrom 2 to 1.airflow-core/tests/unit/models/test_taskinstance.py: the existing test stored the interval as a plain float, so it never exercised the JSON path. It now stores the serialized envelope and is parametrized over a fixedtimedeltaand aVariableInterval. Both cases fail onmainwith the reportedTypeErrorand pass with the fix.The core-side
SerializedVariableIntervalcounterpart is being added separately in #71802.resolve_deadline_alert_interval()is the single place that isinstance-checks the SDK dataclass, so that PR changes it in one spot without moving any call site.closes: #70368
PR Checklist
mainbranchmain)prekhooks pass on the changed files (ruff, ruff-format, mypy for airflow-core, check-sdk-imports)