Repository navigation
fix(airflow): every generated DAG loads on Airflow 3 and on Airflow 2.7+ - #736
Merged
Merged
Conversation
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
`fluid export --engine airflow`, `fluid generate schedule`, `fluid init`, `fluid scaffold-composer`, `fluid schedule-sync` and the AWS, GCP and Snowflake code generators emitted DAG(schedule_interval=...) and start_date=days_ago(1), which Airflow 3 removed (checked against apache/airflow 3.2.2: DAG takes only `schedule`, airflow.utils.dates has no days_ago), so their DAGs failed to import. They now pass schedule=, a fixed start_date with catchup=False, and import DAG / dag / task and the core operators and sensors from their Airflow 3 homes (airflow.sdk, apache-airflow-providers-standard) with an ImportError fallback to the Airflow 2 paths: the shape schedulers/airflow/fluid_apply.py already emitted, now shared as codegen_utils.airflow_dag_import / airflow_standard_import. Loading every rendered DAG into a real DagBag found three more defects, each broken on Airflow 2.10 as well: - Snowflake SQL used SnowflakeOperator, which apache-airflow-providers-snowflake 6.0 removed; it is SQLExecuteQueryOperator (common-sql) with conn_id now. - The GCP generator imported BigQueryCreateEmptyTableOperator, which google provider 14 removed; create_table uses BigQueryCreateTableOperator where it exists and the old operator before that, and the scheduler drops the unused import. The Dataflow operator is imported only when a task uses it: its module needs the apache-beam provider. - The AWS TaskFlow DAG wired its @task functions (`load >> publish`), a TypeError at parse; it wires each @task's result, and an operator as itself. tests/test_airflow3_dag_compat.py renders every generator (15 renders) and fails on schedule_interval, days_ago, an Airflow-2-only import outside its fallback, SnowflakeOperator, or wiring a @task function; against main all 13 original renders fail. Three tests that pinned the old output follow it.
…ies follow the review Review round on the Airflow 3 change. - A @task's result name is allocated against every task variable and every other result: a task named `chk_result` and `chk`'s result shared one, so the edge from `chk_result` silently started at `chk`. - The GCP Dataflow import follows the dispatch (`<any>.dataflow.run_template`), not a `gcp.dataflow.` prefix: `gcp.dataflow.launch_flex` (a PythonOperator) no longer pulls in the beam-dependent import, `dataflow.run_template` gets the import its operator needs, and the import comes before `logger = ...`. - Security, pre-existing: `fluid init`'s basic DAG spliced `orchestration.retries` and `retry_delay` into the DAG source as text, so a contract value ran as Python at DAG-parse time. Both are read as numbers now. - Version facts: google provider 13 added BigQueryCreateTableOperator and 17.0 removed BigQueryCreateEmptyTableOperator (not 14). scaffold-composer and schedule-sync already passed schedule=; for them this only drops the deprecated import paths. CHANGELOG notes that SQLExecuteQueryOperator and BigQueryCreateTableOperator template conn_id/hook_params and table_resource, which their predecessors did not. - Tests: the Snowflake render exercises the SQL path (sf.query/sf.table), the import check also reads `import x`, `from airflow.operators import bash`, `from airflow.models import DAG`, tuple handlers and a fallback importing a different name; new injection tests for a hostile GCP create_table schema and hostile init retries. - The first-dag template README shows the Airflow 2.7+/3 shape.
…nfinite retries fall back
Second review round on the Airflow 3 change; LOW findings only.
- The AWS generators reserve every name their imports bind (`task`, the
operator classes) as well as `dag`: `taskId: task` emitted `def task():`
inside the TaskFlow DAG function, shadowing the decorator, and the DAG failed
to load with UnboundLocalError on Airflow 2 and 3.
- `fluid init`'s retries fall back to 3 on OverflowError too (`retries: .inf`),
instead of skipping the DAG.
- The CHANGELOG's Jinja note gives the escape for `{%` and `{#` as well as `{{`.
…ader The License Headers check (scripts/check_license_headers.py) failed on the new file.
fas89
force-pushed
the
feat/airflow3-dags
branch
from
October 22, 2026 13:22
f29bf7b to
90447ed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every Airflow DAG forge-cli generates now loads on Airflow 3, and still on Airflow 2.7+. This is step 1 of the Command Center's move to Airflow 3: CC runs
fluid export --engine airflowand ships its own Airflow image, and that image moves to 3.2.2 once a forge-cli release carries this.What was broken
Airflow 3 removed two things the generators emitted, checked against apache/airflow 3.2.2:
DAG(schedule_interval=...): the DAG takes onlyschedule, so the old keyword raisesTypeError.airflow.utils.dates.days_ago: removed, so importing it raisesImportError.Those two broke every DAG from:
fluid export --engine airflow(runtimes/airflow_provider_actions.py);fluid generate schedule(schedulers/airflow);fluid init(cli/_init_dag_helpers.py);fluid scaffold-composerandfluid schedule-syncalready passedschedule=. Like every other generator, though, they imported the operators from paths Airflow 3 has deprecated.Loading every rendered DAG into a real
DagBagfound three more defects, each broken on Airflow 2.10 too:SnowflakeOperator, which apache-airflow-providers-snowflake 6.0 removed.BigQueryCreateEmptyTableOperator, removed in google provider 17.0. They also imported the Dataflow operator unconditionally, and its module needs the apache-beam provider.@taskfunctions (load >> publish), which raisesTypeErrorat parse.What changed
codegen_utils.airflow_dag_import()andairflow_standard_import(name). Each emits a try/except that imports from the Airflow 3 home (airflow.sdk, apache-airflow-providers-standard) and falls back to the Airflow 2 path. That is the shapeschedulers/airflow/fluid_apply.pyalready emitted. Every generator uses them, and passesschedule=with a fixed timezone-awarestart_dateandcatchup=False.SQLExecuteQueryOperator(common-sql, installed by the snowflake provider) withconn_id, andhook_paramscarrying the warehouse and database. It keepsSnowflakeOperator's statement splitting and autocommit defaults.create_tableemitsBigQueryCreateTableOperatorwhere the provider has it (13+), else the old operator. The choice is made at runtime in the DAG.<any>.dataflow.run_templatetask, the same test the dispatch uses.@task's result, and each operator as itself. Result names are allocated so they never collide with a task's variable.fluid init(security, pre-existing):orchestration.retriesandretry_delaywere spliced into the DAG source as text, so a contract value ran as Python at DAG-parse time. Both are read as bounded numbers now.templates/first-dag/README.mdshows the new shape.conn_id/hook_params/table_resource.Tests
tests/test_airflow3_dag_compat.pyrenders every generator, 15 renders in all, and fails on any of:schedule_interval;days_ago;from x import y,import x,from airflow.models import DAG, …) outside anexcept ImportErrorfallback whosetryimports the same names from their Airflow 3 home;SnowflakeOperator;@taskfunction wired as a task;Against main, all 13 original renders fail. Two new injection tests cover a hostile GCP
create_tableschema and hostilefluid initretries. Three existing tests that pinned the old output now pin the new one.Borrow before build
Validation
Every rendered DAG was loaded into a real
DagBag.apache/airflow:3.3.2-python3.11image (amazon 9.36, google 22.5, snowflake 6.17, standard 1.19), plus the apache-beam providertask,dag,AthenaOperator,S3CreateBucketOperatorandPythonOperator(classic and TaskFlow), with the right edgesload >> publish), including an operator upstream of a@task.ruff check fluid_build/ tests/andblack==24.10.0 --check fluid_build/ tests/(CI's pins) are clean.tests/perf/test_startup_budget.pypasses.Review
An independent reviewer ran security review, world-class inspection and defect scan. Its own DagBag loads covered every generator branch on both versions, and it checked the compat shims against Airflow 3.0, 3.1 and 3.2.2 and providers-standard 1.0.
@taskresult could reuse another task's variable name;fluid initinjection.OverflowErroronretries: .inf;{%and{#;taskId: taskshadowed the TaskFlow@taskdecorator. The AWS generators now reservetaskand every operator class they import.task,dag,AthenaOperator,S3CreateBucketOperatorandPythonOperatorloads on 3.2.2 and 2.10.5, classic and TaskFlow alike, with the right edges.The security pass found no new injection path. Every new interpolation goes through
py_str_literal,reprorsanitize_identifier.Not in this PR
Pre-existing, filed separately:
fluid initcallsgenerate-airflow --output-dir, a flag that command doesn't accept, so it always uses the basic fallback DAG.pythontasks reference a callable that is never defined.examples/generated_dags/(not shipped) still shows the old shape.Release
The Command Center's Airflow 3 change needs a forge-cli release that carries this.