Repository navigation
Add durable/deferrable filters to the Airflow Registry - #70298
Conversation
04b542e to
ef2d05c
Compare
|
Round 5 wrap-up, so everything outstanding is in one place. All of it is verified against Blocking
Still open from round 4The field-count nit. The stronger assertion in Satisfied, please resolveThese three are collapsed as outdated in the UI, so they are easy to miss, but they are done:
DescriptionTwo statements in the body no longer match the code:
The "1779 modules total, 6 durable, 217 deferrable, 5 with both" line predates both detector rewrites and will move again once the badge question lands, so it is worth re-running at the end rather than now. The Databricks examples still hold; I re-checked the four listed as deferrable-only. Not asking for either of theseListing them so neither surfaces later as a surprise. The |
kaxil
left a comment
There was a problem hiding this comment.
Round-6 items all verified at 4c6aacf: the docstring note, the decorator capability fields (all 24 decorators resolve, @task.agent and @task.kubernetes now badge durable), and the three new tests each fail when the code they pin is reverted.
Two small things before merge, neither blocking. The description's aggregate counts (1779/6/217/5) and the "SparkSubmitOperator is the single durable-only case" line are out of date now that the numbers have settled (AgentOperator and @task.agent are durable-only too, and the delegation walk also flips SparkKubernetesOperator and SnowflakeNotebookOperator to durable, both correctly). Since this gets squash-merged under the body it is worth a refresh. Also, test_module_contract_accepts_legacy_modules_without_supports_deferrable asserts on the input dict because _validate returns its payload, so it passes even with the field deleted or its default flipped; ModuleContract.model_validate(payload).supports_deferrable is False is the binding form (the durable sibling has the same shape).
Optional follow-up material, not for this PR: the round-5 origin-resolution and depth-key fixes have no test that fails without them, and _execute_chain_calls_resumable still substring-matches unstripped source while the deferral path strips comments. The Durable badge tooltip ("Reconnects to an already-running job on retry") also predates this PR and does not describe AgentOperator or @task.agent, whose durable mode replays persisted step state rather than reconnecting to a job.
|
@kaxil Both done. Description refreshed against a fresh Both legacy-module tests now assert on the parsed model rather than the input dict: Taking the rest as follow-up: tests for the round-5 origin-resolution and depth-key |
Feel free to merge after CI 🎉 |
|
The failing checks aren't related. Merging the PR. |
Backport failed to create: v3-3-test. View the failure log Run detailsNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
You can attempt to backport this manually by running: cherry_picker 8a8bd52 v3-3-testThis should apply the commit to the v3-3-test branch and leave the commit in conflict state marking After you have resolved the conflicts, you can continue the backport process by running: cherry_picker --continueIf you don't have cherry-picker installed, see the installation guide. |
Was generative AI tooling used to co-author this PR?
Important Note
What "durable" and "deferrable" mean in the PR: there are two different reasons an operator can survive a crash, and they work in totally different ways. "Durable" means the operator's own code remembers where its job was and reconnects to it instead of starting over, and this operates using task state store in synchronous mode. "Deferrable" is running using a trigger, in asynchronous mode. The end result looks similar to a user (nothing gets redone), but one is a promise the operator has to keep and the other is just how the system already works for everyone.
What
Follow-up to the merged
supports_durable_executionbadge PR (#67236). Now that we have badge support, we should also have a few filters for ease of navigation for two related-but-distinct capabilities: "durable" (anything that survives a crash without redoing work:ResumableJobMixin/manual-marker operators, or deferrable ones) and "deferrable" (the narrower, deferrable-only subset). The registry had no deferrable detection at all before this change.Current behaviour
The Registry only tracks
supports_durable_execution(ResumableJobMixinor the manual__supports_durable_executionmarker). Deferrable operators, a much more common capability across most cloud providers, have no signal anywhere in the pipeline, and there's no way to filter a provider's module list by either capability.Proposed change
is_durable_capable(cls)indev/registry/extract_parameters.pywalks the resolvedexecute()chain fromcls, following bothsuper().execute()and an explicitParentClass.execute(self, context)call (the pattern task decorators like@task.agent/@task.kubernetesuse), to check whether a class that declares the durable marker, or genuinely implementsResumableJobMixin, still preserves that behavior after subclassing. Whole-line comments are stripped before matching so a comment that merely mentions delegation isn't mistaken for real code.supports_deferrable(cls)added todev/registry/extract_parameters.py. Checking for adeferrableconstructor parameter isn't enough, since a subclass can inherit the parameter while overridingexecute()with code that never reads it, and an operator that always defers unconditionally has no such parameter at all. This walks outward fromexecute()into helper methods it calls viaself.<name>(...), and up the MRO through delegation, looking for an actualself.defer(...)call, aself.deferrableread, or aTaskDeferredraise. Code that's only reachable on a core older than the one being imported (e.g. adefer()fallback behindif AIRFLOW_V_3_3_PLUS: raise ..., as in the HITL and common.ai approval operators) is excluded, since it never actually runs on the core the extractor imports.supports_deferrable: bool = Falseadded toModuleContract, wired intomake_entry()alongsidesupports_durable_execution(13 fields total, kept as an independent signal, not merged into the existing one).Changes of Note
supports_durable_execution OR supports_deferrable, checking it shows every deferrable operator plus everyResumableJobMixin/manual-marker operator, even ones that aren't deferrable. "Deferrable only" issupports_deferrablealone, never OR'd. This means an operator that is both durable and deferrable appears under both filters, which is intentional and not confusing in practice, since the two independent badges on the card explain why.is_deferrableinheriting a parent's__init__unmodified still counts as deferrable (unlike thesupports_durable_executionmanual-marker case, which deliberately does not propagate to subclasses), since inheriting an unmodified constructor is a real functional guarantee the parameter still works, whereas inheriting a durable-execution claim says nothing about whether an overriddenexecute()still preserves it.is_durable_capable()itself, not just adds the new deferrable signal alongside it. That function's existing marker-lookup logic had three separate false-negative bugs (an MRO walk that lost track across multiple inheritance, a lookup that gave up on a class with no ownexecuteoverride, and a name-mangling calculation that broke on classes with a leading underscore), all now fixed. This changes the already-shipped Durable badge's output for a few real operators, most notablyEksPodOperatorandGKEStartPodOperator, which were previously misreported as not durable-capable and now correctly show the badge.SparkKubernetesOperatoralso reports durable, correctly, but for a reason the detector doesn't actually check (see the docstring on_delegates_execute_to); its deferrable path never delegates viasuper()at all, and is durable only becauseKubernetesPodOperator'sexecute_sync/execute_asyncboth independently call the pod-reattach mechanism.Testing
Built breeze to extract the provider data using:
breeze registry extract-dataBuilt the actual Eleventy site against this real data and confirmed in the rendered HTML: both badges appear on the right operator cards (e.g. Databricks' page shows both a "Durable" and "Deferrable" badge on
DatabricksRunNowOperator), both filter checkboxes render with the intended labels, and thedata-durable/data-deferrableattributes are correctly populated per the OR/non-OR logic on every module checked.For current changes, this is the behaviour:
DatabricksSubmitRunOperatorandDatabricksRunNowOperatorare both durable and deferrable.DatabricksNotebookOperator,DatabricksSQLStatementsOperator,DatabricksTaskBaseOperator,DatabricksTaskOperator,DatabricksStartWarehouseOperator,DatabricksStopWarehouseOperator, andDatabricksSQLStatementsSensorare deferrable only. The rest of the provider's operators/hooks/sensors are neither.SparkSubmitOperatoris durable but not deferrable (nodeferrableparam).BigQueryInsertJobOperatoris both durable and deferrable, same pattern as Databricks.RedshiftDataOperatoris both durable and deferrable.SnowflakeSqlApiOperatorandSnowflakeNotebookOperatorare both durable and deferrable.Across the full dataset: 1995 modules total, 17 durable, 241 deferrable, 14 with both. The three durable-only entries (durable without a deferral path) are
SparkSubmitOperator,AgentOperator, and the@task.agentdecorator. The 14 that are both:EksPodOperator,GlueJobOperator,RedshiftDataOperator,LivyOperator,KubernetesPodOperator,SparkKubernetesOperator,DatabricksRunNowOperator,DatabricksSubmitRunOperator,BigQueryInsertJobOperator,GKEStartPodOperator,SnowflakeSqlApiOperator,SnowflakeNotebookOperator, and the@task.kubernetes/@task.kubernetes_cmddecorators.Manual verification (
breeze registry extract-data, generateddev/registry/modules.json)Every operator raised across all review rounds, checked against the freshly generated data:
EksPodOperatorGKEStartPodOperatorSparkKubernetesOperator_delegates_execute_todocstring note)SnowflakeNotebookOperatorKubernetesPodOperatorAgentOperatorHITLOperatordefer_for_approvalfix confirmedApprovalOperatorHITLBranchOperatorHITLEntryOperatorLLMOperatorLLMBranchOperatorLLMSQLQueryOperatorLLMSchemaCompareOperatorLLMFileAnalysisOperatorDiscordWebhookOperatorHttpOperatorDatabricksRunNowOperatorDatabricksSubmitRunOperatorKubernetesJobOperatorGKEStartJobOperatorSparkJDBCOperatorexecute_resumable)Some screenshots:
In this image, "Durable (includes deferrable)" is selected, and you can see both durable + deferrable (BigQueryInsertJobOperator) and just deferrable ones selected
"Deferrable only selection":
Operator like snowflake has only one operator with both deferrable and durable property, so its here:
Spark is one of few operators with only "durable", and no deferrable option:
What's next
Interactive click-through verification (actually toggling the checkboxes in a running browser to confirm
filterModules()behaves correctly) hasn't been done -- only the generated HTML/data have been checked, not runtime JS behavior. Registry-wide (cross-provider) discovery -- "which are all the durable/deferrable operators across the whole registry," via a Pagefind search filter facet -- was discussed and deliberately deferred to a separate follow-up.{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.