from airflow.sdk import ...
The only authoring import path in Airflow 3. airflow.decorators, airflow.models, airflow.datasets all still work but warn, and are scheduled for removal.
my_pipeline()
A @dag function must be called at module level. Miss it and the file parses perfectly and registers nothing.
Dataset → Asset
Renamed in 3.0, along with DatasetAlias/All/Any. The concept is unchanged: a URI that producers declare as an outlet and consumers put in schedule=.
schedule= only
schedule_interval= and timetable= are both gone. One argument takes a cron string, a timedelta, a timetable or a list of assets.
catchup defaults to False
Reversed in 3.0. The old default was the reason an unpaused Dag with a year-old start_date would launch hundreds of runs at once.
bare cron → CronTriggerTimetable
It fires at the start of the period rather than waiting for a data interval to close, so logical_date and every ds derived from it shift by one interval on upgrade.
logical_date == run_after
No longer equal to data_interval_start, and it can be None. execution_date is deleted, as are next_ds, prev_ds, tomorrow_ds and yesterday_ds.
BashOperator moved
It, PythonOperator, FileSensor and ExternalTaskSensor live in apache-airflow-providers-standard now. Install it or nothing imports.
no DB access from tasks
Task code talks to the API server through the Task SDK. Session queries and model imports that worked in Airflow 2 now fail — use the Airflow Python Client.
top level runs constantly
Everything outside a task body re-executes on every parse cycle. A Variable.get() or an API call up there is a request every few seconds, forever.
none_failed_min_one_success
The trigger rule for a join after a branch. Under the default all_success, a skipped parent skips the join, which is the single most common "why did nothing run" report.
db migrate, api-server, dag-processor
db init/db upgrade are gone, webserver is renamed, and the Dag processor is now a separate process you must start yourself even locally.
retry_exponential_backoff is a float
A multiplier now: 0 disables, 2.0 doubles each retry. It was a boolean in Airflow 2, so an unchanged True no longer means what you think.
invalid kwargs break the file
allow_illegal_arguments is gone. A misspelled operator argument that Airflow 2 ignored silently now raises TypeError at import and takes the whole Dag file down.