Airflow 3 moved the classes DAG authors use into two places: airflow.sdk for the building blocks (DAG, task, Variable, Param…) and provider packages for the operators, mostly apache-airflow-providers-standard. Many old paths still work with a deprecation warning, a few are gone. The tables show which is which on Airflow 3.3.2.

Copy and paste

The new imports for a typical DAG file:

import pendulum
from airflow.sdk import DAG, dag, task, task_group, TaskGroup, Variable, Param, Asset, TriggerRule
from airflow.sdk import get_current_context
from airflow.providers.standard.operators.bash import BashOperator
from airflow.providers.standard.operators.python import PythonOperator, BranchPythonOperator
from airflow.providers.standard.operators.empty import EmptyOperator
from airflow.providers.standard.operators.trigger_dagrun import TriggerDagRunOperator
from airflow.providers.standard.sensors.external_task import ExternalTaskSensor
from airflow.providers.smtp.operators.smtp import EmailOperator

Fix a folder of DAGs with ruff, then review the changes:

pip install "ruff>=0.13.1"
ruff check dags/ --select AIR3,F401 --fix --unsafe-fixes

Core classes

Airflow 2 Airflow 3 Old path on 3.3.2
from airflow import DAG from airflow.sdk import DAG works
from airflow.models.dag import DAG from airflow.sdk import DAG warning
from airflow.decorators import dag, task, task_group from airflow.sdk import dag, task, task_group warning
from airflow.models import Variable from airflow.sdk import Variable works
from airflow.models.param import Param from airflow.sdk import Param warning
from airflow.models.baseoperator import BaseOperator, chain from airflow.sdk import BaseOperator, chain warning
from airflow.hooks.base import BaseHook from airflow.sdk import BaseHook warning
from airflow.sensors.base import BaseSensorOperator from airflow.sdk import BaseSensorOperator warning
from airflow.utils.task_group import TaskGroup from airflow.sdk import TaskGroup warning
from airflow.operators.python import get_current_context from airflow.sdk import get_current_context warning
from airflow.utils.context import Context from airflow.sdk import Context warning
from airflow.utils.trigger_rule import TriggerRule from airflow.sdk import TriggerRule warning
from airflow.models.connection import Connection from airflow.sdk import Connection works
from airflow.models.xcom_arg import XComArg from airflow.sdk import XComArg warning
from airflow.utils.edgemodifier import Label from airflow.sdk import Label works
from airflow.exceptions import AirflowSkipException from airflow.sdk.exceptions import AirflowSkipException warning
from airflow.datasets import Dataset from airflow.sdk import Asset removed
from airflow.utils.dates import days_ago pendulum.datetime(2026, 1, 1, tz="UTC") removed

Operators and sensors

Airflow 2 Airflow 3 Old path on 3.3.2
airflow.operators.bash.BashOperator airflow.providers.standard.operators.bash.BashOperator warning
airflow.operators.python.PythonOperator (and Branch..., ShortCircuit...) airflow.providers.standard.operators.python warning
airflow.operators.empty.EmptyOperator airflow.providers.standard.operators.empty.EmptyOperator warning
airflow.operators.dummy.DummyOperator airflow.providers.standard.operators.empty.EmptyOperator removed
airflow.operators.trigger_dagrun.TriggerDagRunOperator airflow.providers.standard.operators.trigger_dagrun.TriggerDagRunOperator warning
airflow.sensors.external_task.ExternalTaskSensor airflow.providers.standard.sensors.external_task.ExternalTaskSensor warning
airflow.sensors.filesystem.FileSensor airflow.providers.standard.sensors.filesystem.FileSensor warning
airflow.operators.email.EmailOperator airflow.providers.smtp.operators.smtp.EmailOperator warning
airflow.operators.subdag.SubDagOperator a TaskGroup removed

“Warning” means the import works but logs a DeprecatedImportWarning such as The airflow.operators.bash.BashOperator attribute is deprecated. The upgrade guide says those paths will be removed in a future version, so change them now.

Not only imports

Airflow 2 Airflow 3
schedule_interval="@daily" schedule="@daily"
timetable=MyTimetable() schedule=MyTimetable()
{{ execution_date }}, context["execution_date"] {{ logical_date }}, context["logical_date"]
{{ yesterday_ds }}, {{ tomorrow_ds }} {{ macros.ds_add(ds, -1) }}, {{ macros.ds_add(ds, 1) }}
catchup defaults to True catchup defaults to False
A daily run is dated the day before A daily run is dated the day it runs

schedule_interval and timetable are not just deprecated: DAG(...) rejects them with a TypeError. The last row changes which data a DAG loads; see why your DAG processes yesterday’s data.

What ruff finds and what it misses

Ruff’s AIR rules, from version 0.13.1, flag the imports and arguments above: AIR301 and AIR302 for what is removed or moved in Airflow 3, AIR311 and AIR312 for what still works but is deprecated. On an Airflow 2 DAG:

dags/old_style.py:16:5: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
dags/old_style.py:19:13: AIR302 [*] `airflow.operators.dummy.DummyOperator` is moved into `standard` provider in Airflow 3.0;
dags/old_style.py:20:11: AIR312 `airflow.operators.bash.BashOperator` is deprecated and moved into `standard` provider in Airflow 3.0;

Two details from running it on real files:

grep -rnE "execution_date|yesterday_ds|tomorrow_ds|prev_ds|next_ds" dags/

Tested with Airflow 3.3.2 and ruff 0.17.

References