web-dev-qa-db-fra.com

Méthode appropriée pour créer des flux de travail dynamiques dans Airflow

Problème

Existe-t-il un moyen dans Airflow de créer un flux de travail tel que le nombre de tâches B. * reste inconnu jusqu'à la fin de la tâche A? J'ai examiné les sous-tags mais il semble que cela ne peut fonctionner qu'avec un ensemble statique de tâches devant être déterminées lors de la création de Dag.

Les déclencheurs dag fonctionneraient-ils? Et si oui pourriez-vous s'il vous plaît donner un exemple.

J'ai un problème où il est impossible de connaître le nombre de tâches B qui seront nécessaires pour calculer la tâche C jusqu'à ce que la tâche A soit terminée. Chaque tâche B * nécessite plusieurs heures de calcul et ne peut pas être combinée.

              |---> Task B.1 --|
              |---> Task B.2 --|
 Task A ------|---> Task B.3 --|-----> Task C
              |       ....     |
              |---> Task B.N --|

Idée n ° 1

Je n'aime pas cette solution car je dois créer un ExternalTaskSensor bloquant et toute la tâche B. * prendra entre 2 et 24 heures. Je ne considère donc pas cela comme une solution viable. Il y a sûrement un moyen plus facile? Ou Airflow n'a-t-il pas été conçu pour cela?

Dag 1
Task A -> TriggerDagRunOperator(Dag 2) -> ExternalTaskSensor(Dag 2, Task Dummy B) -> Task C

Dag 2 (Dynamically created DAG though python_callable in TriggerDagrunOperator)
               |-- Task B.1 --|
               |-- Task B.2 --|
Task Dummy A --|-- Task B.3 --|-----> Task Dummy B
               |     ....     |
               |-- Task B.N --|

Modifier 1:

À partir de maintenant, cette question n'a toujours pas de bonne réponse . J'ai été contacté par plusieurs personnes à la recherche d'une solution. 

41
costrouc

Voici comment je l'ai fait avec une requête similaire sans aucun sous-tag:

Commencez par créer une méthode qui renvoie les valeurs souhaitées.

def values_function():
     return values

Méthode de création suivante qui générera les travaux de manière dynamique:

def group(number, **kwargs):
        #load the values if needed in the command you plan to execute
        dyn_value = "{{ task_instance.xcom_pull(task_ids='Push_func') }}"
        return BashOperator(
                task_id='JOB_NAME_{}'.format(number),
                bash_command='script.sh {} {}'.format(dyn_value, number),
                dag=dag)

Et puis combinez-les:

Push_func = PythonOperator(
        task_id='Push_func',
        provide_context=True,
        python_callable=values_function,
        dag=dag)

complete = DummyOperator(
        task_id='All_jobs_completed',
        dag=dag)

for i in values_function():
        Push_func >> group(i) >> complete
15
Oleg Yamin

J'ai trouvé un moyen de créer des flux de travail en fonction du résultat des tâches précédentes.
En gros, vous voulez créer deux sous-dags avec les éléments suivants: 

  1. Xcom Envoie une liste (ou tout ce dont vous avez besoin pour créer le flux de travail dynamique plus tard) dans le sous-répertoire à exécuter en premier (voir test1.py def return_list()
  2. Transmettez l'objet dag principal en tant que paramètre à votre deuxième sous-dialogue
  3. Maintenant, si vous avez l'objet dag principal, vous pouvez l'utiliser pour obtenir une liste de ses instances de tâches. À partir de cette liste d'instances de tâches, vous pouvez filtrer une tâche de l'exécution en cours en utilisant parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1]), vous pouvez probablement ajouter d'autres filtres ici.
  4. Avec cette instance de tâche, vous pouvez utiliser xcom pull pour obtenir la valeur dont vous avez besoin en spécifiant l'identifiant dag_id sur celui du premier sous-dialogue: dag_id='%s.%s' % (parent_dag_name, 'test1').
  5. Utilisez la liste/valeur pour créer vos tâches de manière dynamique

Maintenant, j'ai testé cela dans mon installation de flux d'air locale et cela fonctionne bien. Je ne sais pas si la partie d'extraction xcom rencontrera des problèmes s'il y a plus d'une instance du dag en cours d'exécution en même temps, mais vous utiliserez probablement une clé unique ou quelque chose comme ça pour identifier de manière unique le xcom. la valeur que vous souhaitez ........ On pourrait probablement optimiser 3. l'étape pour être sûr à 100% d'obtenir une tâche spécifique du tableau principal actuel, mais pour mon utilisation, cela fonctionne assez bien, je pense qu'il suffit d'un objet task_instance à utiliser xcom_pull.

De plus, je nettoie les xcom du premier sous-répertoire avant chaque exécution, juste pour m'assurer que je n'obtiens pas par inadvertance une valeur erronée.

Je suis assez mauvais pour expliquer, alors j'espère que le code suivant rendra tout clair:

test1.py

from airflow.models import DAG
import logging
from airflow.operators.python_operator import PythonOperator
from airflow.operators.postgres_operator import PostgresOperator

log = logging.getLogger(__name__)


def test1(parent_dag_name, start_date, schedule_interval):
    dag = DAG(
        '%s.test1' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date,
    )

    def return_list():
        return ['test1', 'test2']

    list_extract_folder = PythonOperator(
        task_id='list',
        dag=dag,
        python_callable=return_list
    )

    clean_xcoms = PostgresOperator(
        task_id='clean_xcoms',
        postgres_conn_id='airflow_db',
        sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
        dag=dag)

    clean_xcoms >> list_extract_folder

    return dag

test2.py

from airflow.models import DAG, settings
import logging
from airflow.operators.dummy_operator import DummyOperator

log = logging.getLogger(__name__)


def test2(parent_dag_name, start_date, schedule_interval, parent_dag=None):
    dag = DAG(
        '%s.test2' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date
    )

    if len(parent_dag.get_active_runs()) > 0:
        test_list = parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1].xcom_pull(
            dag_id='%s.%s' % (parent_dag_name, 'test1'),
            task_ids='list')
        if test_list:
            for i in test_list:
                test = DummyOperator(
                    task_id=i,
                    dag=dag
                )

    return dag

et le flux de travail principal:

test.py

from datetime import datetime
from airflow import DAG
from airflow.operators.subdag_operator import SubDagOperator
from subdags.test1 import test1
from subdags.test2 import test2

DAG_NAME = 'test-dag'

dag = DAG(DAG_NAME,
          description='Test workflow',
          catchup=False,
          schedule_interval='0 0 * * *',
          start_date=datetime(2018, 8, 24))

test1 = SubDagOperator(
    subdag=test1(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval),
    task_id='test1',
    dag=dag
)

test2 = SubDagOperator(
    subdag=test2(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval,
                 parent_dag=dag),
    task_id='test2',
    dag=dag
)

test1 >> test2
4
Christopher Beck

Oui, c'est possible. J'ai créé un exemple de DAG qui le montre bien.

import airflow
from airflow.operators.python_operator import PythonOperator
import os
from airflow.models import Variable
import logging
from airflow import configuration as conf
from airflow.models import DagBag, TaskInstance
from airflow import DAG, settings
from airflow.operators.bash_operator import BashOperator

main_dag_id = 'DynamicWorkflow2'

args = {
    'owner': 'airflow',
    'start_date': airflow.utils.dates.days_ago(2),
    'provide_context': True
}

dag = DAG(
    main_dag_id,
    schedule_interval="@once",
    default_args=args)


def start(*args, **kwargs):

    value = Variable.get("DynamicWorkflow_Group1")
    logging.info("Current DynamicWorkflow_Group1 value is " + str(value))


def resetTasksStatus(task_id, execution_date):
    logging.info("Resetting: " + task_id + " " + execution_date)

    dag_folder = conf.get('core', 'DAGS_FOLDER')
    dagbag = DagBag(dag_folder)
    check_dag = dagbag.dags[main_dag_id]
    session = settings.Session()

    my_task = check_dag.get_task(task_id)
    ti = TaskInstance(my_task, execution_date)
    state = ti.current_state()
    logging.info("Current state of " + task_id + " is " + str(state))
    ti.set_state(None, session)
    state = ti.current_state()
    logging.info("Updated state of " + task_id + " is " + str(state))


def bridge1(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 2

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group2 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group2 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.Apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('secondGroup_' + str(i), str(kwargs['execution_date']))


def bridge2(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 3

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group3 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group3 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.Apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('thirdGroup_' + str(i), str(kwargs['execution_date']))


def end(*args, **kwargs):
    logging.info("Ending")


def doSomeWork(name, index, *args, **kwargs):
    # Do whatever work you need to do
    # Here I will just create a new file
    os.system('touch /home/ec2-user/airflow/' + str(name) + str(index) + '.txt')


starting_task = PythonOperator(
    task_id='start',
    dag=dag,
    provide_context=True,
    python_callable=start,
    op_args=[])

# Used to connect the stream in the event that the range is zero
bridge1_task = PythonOperator(
    task_id='bridge1',
    dag=dag,
    provide_context=True,
    python_callable=bridge1,
    op_args=[])

DynamicWorkflow_Group1 = Variable.get("DynamicWorkflow_Group1")
logging.info("The current DynamicWorkflow_Group1 value is " + str(DynamicWorkflow_Group1))

for index in range(int(DynamicWorkflow_Group1)):
    dynamicTask = PythonOperator(
        task_id='firstGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['firstGroup', index])

    starting_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge1_task)

# Used to connect the stream in the event that the range is zero
bridge2_task = PythonOperator(
    task_id='bridge2',
    dag=dag,
    provide_context=True,
    python_callable=bridge2,
    op_args=[])

DynamicWorkflow_Group2 = Variable.get("DynamicWorkflow_Group2")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group2))

for index in range(int(DynamicWorkflow_Group2)):
    dynamicTask = PythonOperator(
        task_id='secondGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['secondGroup', index])

    bridge1_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge2_task)

ending_task = PythonOperator(
    task_id='end',
    dag=dag,
    provide_context=True,
    python_callable=end,
    op_args=[])

DynamicWorkflow_Group3 = Variable.get("DynamicWorkflow_Group3")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group3))

for index in range(int(DynamicWorkflow_Group3)):

    # You can make this logic anything you'd like
    # I chose to use the PythonOperator for all tasks
    # except the last task will use the BashOperator
    if index < (int(DynamicWorkflow_Group3) - 1):
        dynamicTask = PythonOperator(
            task_id='thirdGroup_' + str(index),
            dag=dag,
            provide_context=True,
            python_callable=doSomeWork,
            op_args=['thirdGroup', index])
    else:
        dynamicTask = BashOperator(
            task_id='thirdGroup_' + str(index),
            bash_command='touch /home/ec2-user/airflow/thirdGroup_' + str(index) + '.txt',
            dag=dag)

    bridge2_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(ending_task)

# If you do not connect these then in the event that your range is ever zero you will have a disconnection between your stream
# and your tasks will run simultaneously instead of in your desired stream order.
starting_task.set_downstream(bridge1_task)
bridge1_task.set_downstream(bridge2_task)
bridge2_task.set_downstream(ending_task)

Avant d’exécuter le DAG, créez ces trois variables de flux d’air

airflow variables --set DynamicWorkflow_Group1 1

airflow variables --set DynamicWorkflow_Group2 0

airflow variables --set DynamicWorkflow_Group3 0

Vous verrez que le DAG part de ça

enter image description here

Pour cela après qu'il a couru

enter image description here

Vous pouvez voir plus d'informations sur ce DAG dans mon article sur la création de Dynamic Workflows On Airflow .

3
Kyle Bridenstine

OA: "Est-il possible dans Airflow de créer un flux de travail tel que le nombre de tâches B. * ne soit pas connu jusqu'à la fin de la tâche A?"

La réponse courte est non. Airflow créera le flux DAG avant de commencer à l'exécuter.

Cela dit, nous sommes arrivés à une conclusion simple, à savoir que nous n’avons pas besoin de cela… .. Lorsque vous souhaitez mettre en parallèle certains travaux, vous devez évaluer les ressources dont vous disposez et non le nombre d’articles à traiter.

Nous l’avons fait comme ceci: nous générons dynamiquement un nombre fixe de tâches, disons 10, qui diviseront le travail. Par exemple, si nous devons traiter 100 fichiers, chaque tâche en traitera 10. Je posterai le code plus tard aujourd'hui.

Mettre à jour

Voici le code, désolé pour le retard.

from datetime import datetime, timedelta

import airflow
from airflow.operators.dummy_operator import DummyOperator

args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2018, 1, 8),
    'email': ['[email protected]'],
    'email_on_failure': True,
    'email_on_retry': True,
    'retries': 1,
    'retry_delay': timedelta(seconds=5)
}

dag = airflow.DAG(
    'parallel_tasks_v1',
    schedule_interval="@daily",
    catchup=False,
    default_args=args)

# You can read this from variables
parallel_tasks_total_number = 10

start_task = DummyOperator(
    task_id='start_task',
    dag=dag
)


# Creates the tasks dynamically.
# Each one will elaborate one chunk of data.
def create_dynamic_task(current_task_number):
    return DummyOperator(
        provide_context=True,
        task_id='parallel_task_' + str(current_task_number),
        python_callable=parallelTask,
        # your task will take as input the total number and the current number to elaborate a chunk of total elements
        op_args=[current_task_number, int(parallel_tasks_total_number)],
        dag=dag)


end = DummyOperator(
    task_id='end',
    dag=dag)

for page in range(int(parallel_tasks_total_number)):
    created_task = create_dynamic_task(page)
    start_task >> created_task
    created_task >> end

Explication du code:

Nous avons ici une tâche de démarrage unique et une tâche de fin unique (les deux factices).

Ensuite, à partir de la tâche de démarrage avec la boucle for, nous créons 10 tâches avec le même appelable python. Les tâches sont créées dans la fonction create_dynamic_task.

À chaque appelant python, nous passons en arguments le nombre total de tâches parallèles et l'index de tâches actuel. 

Supposons que vous ayez 1000 éléments à élaborer: la première tâche recevra l’intrant qu’elle élaborera le premier bloc sur 10. Il divisera les 1000 articles en 10 morceaux et élaborera le premier.

1
Ena

J'ai trouvé ceci Poste moyen qui est très similaire à cette question. Cependant, il est plein de fautes de frappe et ne fonctionne pas lorsque j'ai essayé de le mettre en œuvre.

Ma réponse à ce qui précède est la suivante: 

Si vous créez des tâches de manière dynamique, vous devez le faire en effectuant une itération sur un élément non créé par une tâche en amont ou pouvant être défini indépendamment de cette tâche. J'ai appris qu'il était impossible de transmettre des dates d'exécution ou d'autres variables de flux d'air à un élément extérieur à un modèle (par exemple, une tâche), comme beaucoup d'autres l'ont déjà souligné. Voir aussi ce post

0
Mark Matthews

Le graphique des travaux n'est pas généré au moment de l'exécution. Le graphique est plutôt construit quand il est récupéré par Airflow à partir de votre dossier dags. Par conséquent, il ne sera pas vraiment possible d'avoir un graphique différent pour le travail à chaque exécution. Vous pouvez configurer un travail pour créer un graphique basé sur une requête à charge heure. Ce graphique restera le même pour chaque exécution ultérieure, ce qui n’est probablement pas très utile.

Vous pouvez concevoir un graphique qui exécute différentes tâches à chaque exécution en fonction des résultats de la requête à l'aide d'un opérateur de branche.

Ce que j'ai fait est de préconfigurer un ensemble de tâches, puis de récupérer les résultats de la requête et de les répartir entre les tâches. Quoi qu'il en soit, cela est probablement préférable, car si votre requête renvoie de nombreux résultats, vous ne souhaiterez probablement pas inonder le planificateur de nombreuses tâches simultanées. Pour être encore plus en sécurité, j'ai également utilisé un pool pour m'assurer que mon accès concurrentiel ne devienne pas incontrôlable avec une requête d'une taille inattendue.

"""
 - This is an idea for how to invoke multiple tasks based on the query results
"""
import logging
from datetime import datetime

from airflow import DAG
from airflow.hooks.postgres_hook import PostgresHook
from airflow.operators.mysql_operator import MySqlOperator
from airflow.operators.python_operator import PythonOperator, BranchPythonOperator
from include.run_celery_task import runCeleryTask

########################################################################

default_args = {
    'owner': 'airflow',
    'catchup': False,
    'depends_on_past': False,
    'start_date': datetime(2019, 7, 2, 19, 50, 00),
    'email': ['rotten@stackoverflow'],
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 0,
    'max_active_runs': 1
}

dag = DAG('dynamic_tasks_example', default_args=default_args, schedule_interval=None)

totalBuckets = 5

get_orders_query = """
select 
    o.id,
    o.customer
from 
    orders o
where
    o.created_at >= current_timestamp at time zone 'UTC' - '2 days'::interval
    and
    o.is_test = false
    and
    o.is_processed = false
"""

###########################################################################################################

# Generate a set of tasks so we can parallelize the results
def createOrderProcessingTask(bucket_number):
    return PythonOperator( 
                           task_id=f'order_processing_task_{bucket_number}',
                           python_callable=runOrderProcessing,
                           pool='order_processing_pool',
                           op_kwargs={'task_bucket': f'order_processing_task_{bucket_number}'},
                           provide_context=True,
                           dag=dag
                          )


# Fetch the order arguments from xcom and doStuff() to them
def runOrderProcessing(task_bucket, **context):
    orderList = context['ti'].xcom_pull(task_ids='get_open_orders', key=task_bucket)

    if orderList is not None:
        for order in orderList:
            logging.info(f"Processing Order with Order ID {order[order_id]}, customer ID {order[customer_id]}")
            doStuff(**op_kwargs)


# Discover the orders we need to run and group them into buckets for processing
def getOpenOrders(**context):
    myDatabaseHook = PostgresHook(postgres_conn_id='my_database_conn_id')

    # initialize the task list buckets
    tasks = {}
    for task_number in range(0, totalBuckets):
        tasks[f'order_processing_task_{task_number}'] = []

    # populate the task list buckets
    # distribute them evenly across the set of buckets
    resultCounter = 0
    for record in myDatabaseHook.get_records(get_orders_query):

        resultCounter += 1
        bucket = (resultCounter % totalBuckets)

        tasks[f'order_processing_task_{bucket}'].append({'order_id': str(record[0]), 'customer_id': str(record[1])})

    # Push the order lists into xcom
    for task in tasks:
        if len(tasks[task]) > 0:
            logging.info(f'Task {task} has {len(tasks[task])} orders.')
            context['ti'].xcom_Push(key=task, value=tasks[task])
        else:
            # if we didn't have enough tasks for every bucket
            # don't bother running that task - remove it from the list
            logging.info(f"Task {task} doesn't have any orders.")
            del(tasks[task])

    return list(tasks.keys())

###################################################################################################


# this just makes sure that there aren't any dangling xcom values in the database from a crashed dag
clean_xcoms = MySqlOperator(
    task_id='clean_xcoms',
    mysql_conn_id='airflow_db',
    sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
    dag=dag)


# Ideally we'd use BranchPythonOperator() here instead of PythonOperator so that if our
# query returns fewer results than we have buckets, we don't try to run them all.
# Unfortunately I couldn't get BranchPythonOperator to take a list of results like the
# documentation says it should (Airflow 1.10.2). So we call all the bucket tasks for now.
get_orders_task = PythonOperator(
                                 task_id='get_orders',
                                 python_callable=getOpenOrders,
                                 provide_context=True,
                                 dag=dag
                                )
open_order_task.set_upstream(clean_xcoms)

# set up the parallel tasks -- these are configured at compile time, not at run time:
for bucketNumber in range(0, totalBuckets):
    taskBucket = createOrderProcessingTask(bucketNumber)
    taskBucket.set_upstream(get_orders_task)


###################################################################################################
0
rotten

Je pense avoir trouvé une meilleure solution à ce problème sur https://github.com/mastak/airflow_multi_dagrun , qui utilise la mise en file d'attente simple de DagRuns en déclenchant plusieurs dagruns, comme TriggerDagRuns . La plupart des crédits vont à https://github.com/mastak , bien que je devais corriger quelques détails pour que cela fonctionne avec le flux d'air le plus récent.

La solution utilise un opérateur personnalisé qui déclenche plusieurs DagRuns :

from airflow import settings
from airflow.models import DagBag
from airflow.operators.dagrun_operator import DagRunOrder, TriggerDagRunOperator
from airflow.utils.decorators import apply_defaults
from airflow.utils.state import State
from airflow.utils import timezone


class TriggerMultiDagRunOperator(TriggerDagRunOperator):
    CREATED_DAGRUN_KEY = 'created_dagrun_key'

    @apply_defaults
    def __init__(self, op_args=None, op_kwargs=None,
                 *args, **kwargs):
        super(TriggerMultiDagRunOperator, self).__init__(*args, **kwargs)
        self.op_args = op_args or []
        self.op_kwargs = op_kwargs or {}

    def execute(self, context):

        context.update(self.op_kwargs)
        session = settings.Session()
        created_dr_ids = []
        for dro in self.python_callable(*self.op_args, **context):
            if not dro:
                break
            if not isinstance(dro, DagRunOrder):
                dro = DagRunOrder(payload=dro)

            now = timezone.utcnow()
            if dro.run_id is None:
                dro.run_id = 'trig__' + now.isoformat()

            dbag = DagBag(settings.DAGS_FOLDER)
            trigger_dag = dbag.get_dag(self.trigger_dag_id)
            dr = trigger_dag.create_dagrun(
                run_id=dro.run_id,
                execution_date=now,
                state=State.RUNNING,
                conf=dro.payload,
                external_trigger=True,
            )
            created_dr_ids.append(dr.id)
            self.log.info("Created DagRun %s, %s", dr, now)

        if created_dr_ids:
            session.commit()
            context['ti'].xcom_Push(self.CREATED_DAGRUN_KEY, created_dr_ids)
        else:
            self.log.info("No DagRun created")
        session.close()

Vous pouvez ensuite soumettre plusieurs dagruns à partir de la fonction appelable dans votre PythonOperator, par exemple:

from airflow.operators.dagrun_operator import DagRunOrder
from airflow.models import DAG
from airflow.operators import TriggerMultiDagRunOperator
from airflow.utils.dates import days_ago


def generate_dag_run(**kwargs):
    for i in range(10):
        order = DagRunOrder(payload={'my_variable': i})
        yield order

args = {
    'start_date': days_ago(1),
    'owner': 'airflow',
}

dag = DAG(
    dag_id='simple_trigger',
    max_active_runs=1,
    schedule_interval='@hourly',
    default_args=args,
)

gen_target_dag_run = TriggerMultiDagRunOperator(
    task_id='gen_target_dag_run',
    dag=dag,
    trigger_dag_id='common_target',
    python_callable=generate_dag_run
)

J'ai créé un fork avec le code à https://github.com/flinz/airflow_multi_dagrun

0
flinz