How to Schedule an Airflow DAG (Extract, Transform, Load)
Wrapping a Python ETL script in an Airflow DAG gives you retries, dependency tracking, and visibility into every run - none of which cron can offer. The pattern is three PythonOperator tasks linked with >> and a daily schedule. Here's how to build it.
From cron to a DAG
A bare python3 etl.py cron job runs blindly: if it fails at 2 AM nobody knows until
morning, there is no retry, and there is no record of which step broke. Airflow replaces
that with a DAG (Directed Acyclic Graph) - a set of tasks with explicit dependencies,
so Airflow knows to run transform only after extract succeeds, and to retry on failure
before alerting.
The DAG file
Create dags/daily_etl.py. The modern idiom is a with DAG(...) context manager:
import csv, json
from datetime import datetime, timedelta
from airflow import DAG
try:
from airflow.providers.standard.operators.python import PythonOperator
except ImportError: # older Airflow
from airflow.operators.python import PythonOperator
def extract():
rows = []
with open("/tmp/raw_orders.csv") as f:
for row in csv.DictReader(f):
rows.append(row)
with open("/tmp/raw_orders.json", "w") as f:
json.dump(rows, f)
print(f"extracted {len(rows)} rows")
def transform():
with open("/tmp/raw_orders.json") as f:
rows = json.load(f)
for r in rows:
r["total"] = float(r["price"]) * int(r["quantity"])
with open("/tmp/transformed_orders.json", "w") as f:
json.dump(rows, f)
def load():
with open("/tmp/transformed_orders.json") as f:
rows = json.load(f)
total = sum(r["total"] for r in rows)
with open("/tmp/load_summary.txt", "w") as f:
f.write(f"rows={len(rows)} total_revenue={total:.2f}\n")
default_args = {
"owner": "data-team",
"retries": 2,
"retry_delay": timedelta(seconds=10),
}
with DAG(
dag_id="daily_etl",
schedule="@daily",
start_date=datetime(2024, 1, 1),
catchup=False,
default_args=default_args,
) as dag:
t_extract = PythonOperator(task_id="extract", python_callable=extract)
t_transform = PythonOperator(task_id="transform", python_callable=transform)
t_load = PythonOperator(task_id="load", python_callable=load)
t_extract >> t_transform >> t_load
Key parameters to understand
catchup=False- without this, Airflow back-fills every missed daily run sincestart_date. Almost always set it toFalseon new DAGs.schedule="@daily"- shorthand for0 0 * * *. You can use any cron expression or Airflow's@hourly,@weekly,@monthlyaliases.retries+retry_delayindefault_argsapplies to every task in the DAG; you can override per task by passingretries=directly toPythonOperator.>>is the dependency operator.t_extract >> t_transform >> t_loadchains all three in one line.<<reverses direction.
Testing without a scheduler
airflow dags test runs the DAG in-process - no scheduler, no worker, no database
writes - which makes it ideal for local/CI verification:
export AIRFLOW_HOME=/tmp/airflow
export AIRFLOW__CORE__DAGS_FOLDER=/workspace/dags
export AIRFLOW__CORE__LOAD_EXAMPLES=False
airflow dags test daily_etl 2024-01-01
All three tasks run sequentially and their output files appear under /tmp/.
If a task fails, the traceback shows immediately - no need to dig through the UI.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Wrapping Python ETL functions in PythonOperator tasks
- Chaining task dependencies with >> and setting catchup=False
- Running airflow dags test to verify the DAG end-to-end
FAQ
What is an Airflow DAG and why use it instead of cron?
A DAG (Directed Acyclic Graph) is a set of tasks with defined dependencies and a schedule. Unlike cron, Airflow retries failed tasks, records every run's status, visualizes the dependency graph, and alerts on failure - so you know exactly which step broke and why.
How do I set task dependencies in Airflow?
Use the >> operator between task objects: t_extract >> t_transform >> t_load. This tells Airflow to run transform only after extract succeeds, and load only after transform succeeds.
How do I test an Airflow DAG without running the full scheduler?
Run airflow dags test <dag_id> <execution_date>. It executes all tasks in-process in dependency order - no scheduler, worker, or triggerer needed. This is the standard way to verify a DAG before deploying it.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →