To do


Objective

Minimize required arguments for creating DAGs and tasks by auto-generating or inferring them where possible. Take advantage of Python syntax and processing to streamline code.

Why?

Because Airflow's strength is "workflows as code" and the more streamlined we can make that code, the better.

Because having many repeated [and unnecessary] parameters makes code unwieldy and fragile.

Because even the simplest Airflow workflows require a lot of boilerplate setup, and that's an impediment to easy adoption and widespread use. And complex workflows can get lost behind all the code and repeated declarations. Compared to many workflow managers, Airflow code is easy to grok. But for someone without knowledge of Airflow, it's still hard to understand what's going on.

How?

Illustrative Example

Typical [wordy] setup:

dag = airflow.DAG(
    dag_id='my_dag',
    default_args=dict(
        owner='jlowin',
        start_date=datetime(2015, 1, 1)
  )
)

def fn_1():
  msg = "Hello, world!"
  print(msg)
  return msg

op_1 = airflow.PythonOperator(
    task_id='op_1',
    dag=dag,
    python_callable=fn_1
)

def fn_2():
  msg = "Goodbye, world!"
  print(msg)
  return msg

op_2 = airflow.PythonOperator(
    task_id='op_2',
    dag=dag,
    python_callable=fn_2
)

op_3 = airflow.BashOperator(
    task_id='op_3',
    dag=dag,
    bash_command='echo "Hello from bash, world!"'
)

op_1.set_downstream(op_2)
op_2.set_downstream(op_3)

Proposed [streamlined] setup of the same workflow. This extreme case is totally over the top, just trying to show lots of ideas at once:

# create the dag
dag = airflow.DAG('my_dag')

# use dag as a context manager
with dag:

    @airflow.task
    def fn_1():
      msg = "Hello, world!"
      print(msg)
      return msg

    @airflow.task
    def fn_2():
      msg = "Goodbye, world!"
      print(msg)
      return msg

    @airflow.bash_task(upstream=fn_2)
    def fn_3():
      'echo "Hello from bash, world!"'

    # two ways to set dependencies (other than decorator arguments)
    fn_1 | fn_2
    # OR
    fn_2(fn_1)