How-to guides

These guides cover common model construction, rendering, scheduling, resource, and serialization tasks.

How to build a DAG in Python

Construct operator-specific task models and place them in a Dag:

from airflow_pydantic import BashTask, Dag, PythonTask

dag = Dag(
    dag_id="daily-etl",
    schedule="0 2 * * *",
    start_date="2025-01-01",
    catchup=False,
    default_args={"owner": "data", "retries": 2},
    tasks={
        "extract": BashTask(bash_command="python /opt/etl/extract.py"),
        "load": PythonTask(
            python_callable="builtins.print",
            op_kwargs={"table": "events"},
            dependencies=["extract"],
        ),
    },
)

Task dictionary keys become task_id values when the DAG is rendered or instantiated. dependencies contains upstream task IDs.

How to render deployable Python

Render one DAG model and write the result:

from pathlib import Path

source = dag.render()
Path("generated/daily_etl.py").parent.mkdir(parents=True, exist_ok=True)
Path("generated/daily_etl.py").write_text(source)

To render a complete airflow-config collection, use Configuration.generate() instead. It avoids rewriting unchanged files.

How to instantiate models in memory

Instantiate a model inside an Airflow DAG context:

from airflow import DAG

with DAG(dag_id="daily-etl", schedule=None) as airflow_dag:
    dag.instantiate(dag=airflow_dag)

For declarative configurations, Configuration.generate_in_mem() registers every configured DAG in the calling module.

How to use sensors and branching

Mix sensors and operators in one task mapping:

from airflow_pydantic import BranchPythonTask, FileSensor

dag.tasks.update(
    {
        "wait-for-input": FileSensor(
            filepath="/data/incoming/events.json",
            poke_interval=30,
            timeout=3600,
        ),
        "choose-path": BranchPythonTask(
            python_callable="builtins.print",
            dependencies=["wait-for-input"],
        ),
    }
)

Refer to the API reference for every operator and sensor model.

How to use custom timetables

Pass a timetable model as the DAG schedule:

from datetime import timedelta

from airflow_pydantic import CronTriggerTimetable, MultipleCronTriggerTimetable

dag.schedule = CronTriggerTimetable(
    cron="0 9 * * mon-fri",
    timezone="America/New_York",
    interval=timedelta(hours=1),
)

multiple = MultipleCronTriggerTimetable(
    crons=["0 9 * * mon", "0 9 * * thu"],
    timezone="America/New_York",
)

Available timetable models also include data-interval, delta, and event timetables.

How to configure pools and variables

Use a pool model anywhere a task accepts pool:

from airflow_pydantic import BashTask, Pool, Variable

task = BashTask(
    bash_command="python /opt/jobs/large.py",
    pool=Pool(pool="warehouse", slots=8, description="Warehouse capacity"),
    pool_slots=2,
)

variable = Variable(key="report_settings", deserialize_json=True)

Models defer Airflow database access until runtime. A Variable can therefore remain serializable inside configuration.

How to define typed DAG parameters

Attach Airflow Param definitions to the DAG model:

from airflow_pydantic import Dag, Param

parameterized = Dag(
    dag_id="parameterized-report",
    params={
        "limit": Param(
            100,
            type="integer",
            minimum=1,
            title="Row limit",
        )
    },
)

Tasks can access the value through Airflow templates such as {{ params.limit }}.

How to run remote and downstream workflows

Use the SSH and trigger models for provider-backed operations:

from airflow_pydantic import SSHTask, TriggerDagRunTask

remote = SSHTask(
    ssh_conn_id="reports-host",
    command="python /opt/jobs/report.py",
)

trigger = TriggerDagRunTask(
    trigger_dag_id="downstream-report",
    wait_for_completion=True,
    dependencies=["remote"],
)

Install the matching Airflow providers in the scheduler and worker environment.

How to reuse task settings in Python

Pass an argument model through template:

from airflow_pydantic import BashTask, BashTaskArgs

shell_defaults = BashTaskArgs(
    retries=2,
    env={"MODE": "production"},
)

report = BashTask(
    template=shell_defaults,
    bash_command="python /opt/jobs/report.py",
)

Fields on the concrete task override fields inherited from the template.

How to compose shell environments

Build activation and working-directory commands with BashCommands:

from airflow_pydantic import BashCommands

command = BashCommands(
    commands=["source /opt/venv/bin/activate", "python report.py"],
    cwd="/opt/reports",
    login=True,
)

print(str(command))

Use in_virtualenv(), in_conda(), in_bash(), and link() for the matching single-purpose command transformations.

How to serialize and validate models

Use normal Pydantic methods for storage and round trips:

payload = dag.model_dump(mode="json")
restored = Dag.model_validate(payload)

json_payload = dag.model_dump_json(indent=2)
restored_from_json = Dag.model_validate_json(json_payload)

Serialization retains the import paths needed to reconstruct operator, callable, timetable, and connection types.

How to use models with airflow-config

Select exported model classes with Hydra _target_ entries:

dags:
  remote-report:
    schedule: "@daily"
    tasks:
      report:
        _target_: airflow_pydantic.SSHTask
        ssh_conn_id: reports-host
        command: python /opt/jobs/report.py

See the airflow-config tutorial for loading and materialization.

How to integrate long-running and imported schedules

Use task models from the runtime integration that owns the job: