web-dev-qa-db-fra.com

Flux d'air à l'aide de fichiers de modèle pour PythonOperator

La méthode permettant à un BashOperator ou SqlOperator de récupérer un fichier externe pour son modèle est assez bien documentée, mais l'examen de la PythonOperator mon test de ce que je comprends des documents ne fonctionne pas. Je ne suis pas sûr de savoir comment les paramètres templates_exts et templates_dict interagiraient correctement pour prendre un fichier.

Dans mon dossier dags, j'ai créé: pyoptemplate.sql et pyoptemplate.t ainsi que test_python_operator_template.py:

pyoptemplate.sql:

SELECT * FROM {{params.table}};

pyoptemplate.t:

SELECT * FROM {{params.table}};

test_python_operator_template.py:

# coding: utf-8
# vim:ai:si:et:sw=4 ts=4 tw=80
"""
# A Test of Templates in PythonOperator
"""

from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from datetime import datetime

import pprint

pp = pprint.PrettyPrinter(indent=4)


def templated_function(ds, **kwargs):
    """This function will try to use templates loaded from external files"""
    pp.pprint(ds)
    pp.pprint(kwargs)


# Define the DAG
dag = DAG(dag_id='test_python_operator_template_dag',
          default_args={"owner": "lamblin",
                        "start_date": datetime.now()},
          template_searchpath=['/Users/daniellamblin/airflow/dags'],
          schedule_interval='@once')


# Define the single task in this controller example DAG
op = PythonOperator(task_id='test_python_operator_template',
                    provide_context=True,
                    python_callable=templated_function,
                    templates_dict={
                        'pyoptemplate': '',
                        'pyoptemplate.sql': '',
                        'sql': 'pyoptemplate',
                        'file1':'pyoptemplate.sql',
                        'file2':'pyoptemplate.t',
                        'table': '{{params.table}}'},
                    templates_exts=['.sql','.t'],
                    params={'condition_param': True,
                            'message': 'Hello World',
                            'table': 'TEMP_TABLE'},
                    dag=dag)

Le résultat d'une exécution montre que table a été correctement modélisé en tant que chaîne, mais les autres n'ont extrait aucun fichier à des fins de modélisation.

dlamblin$ airflow test test_python_operator_template_dag test_python_operator_template 2017-01-18
[2017-01-18 23:58:06,698] {__init__.py:36} INFO - Using executor SequentialExecutor
[2017-01-18 23:58:07,342] {models.py:154} INFO - Filling up the DagBag from /Users/daniellamblin/airflow/dags
[2017-01-18 23:58:07,620] {models.py:1196} INFO - 
--------------------------------------------------------------------------------
Starting attempt 1 of 1
--------------------------------------------------------------------------------

[2017-01-18 23:58:07,620] {models.py:1219} INFO - Executing <Task(PythonOperator): test_python_operator_template> on 2017-01-18 00:00:00
'2017-01-18'
{   u'END_DATE': '2017-01-18',
    u'conf': <module 'airflow.configuration' from '/Library/Python/2.7/site-packages/airflow/configuration.pyc'>,
    u'dag': <DAG: test_python_operator_template_dag>,
    u'dag_run': None,
    u'ds_nodash': u'20170118',
    u'end_date': '2017-01-18',
    u'execution_date': datetime.datetime(2017, 1, 18, 0, 0),
    u'latest_date': '2017-01-18',
    u'macros': <module 'airflow.macros' from '/Library/Python/2.7/site-packages/airflow/macros/__init__.pyc'>,
    u'params': {   'condition_param': True,
                   'message': 'Hello World',
                   'table': 'TEMP_TABLE'},
    u'run_id': None,
    u'tables': None,
    u'task': <Task(PythonOperator): test_python_operator_template>,
    u'task_instance': <TaskInstance: test_python_operator_template_dag.test_python_operator_template 2017-01-18 00:00:00 [running]>,
    u'task_instance_key_str': u'test_python_operator_template_dag__test_python_operator_template__20170118',
    'templates_dict': {   'file1': u'pyoptemplate.sql',
                          'file2': u'pyoptemplate.t',
                          'pyoptemplate': u'',
                          'pyoptemplate.sql': u'',
                          'sql': u'pyoptemplate',
                          'table': u'TEMP_TABLE'},
    u'test_mode': True,
    u'ti': <TaskInstance: test_python_operator_template_dag.test_python_operator_template 2017-01-18 00:00:00 [running]>,
    u'tomorrow_ds': '2017-01-19',
    u'tomorrow_ds_nodash': u'20170119',
    u'ts': '2017-01-18T00:00:00',
    u'ts_nodash': u'20170118T000000',
    u'yesterday_ds': '2017-01-17',
    u'yesterday_ds_nodash': u'20170117'}
[2017-01-18 23:58:07,634] {python_operator.py:67} INFO - Done. Returned value was: None
15
dlamblin

Depuis Airflow 1.8, la façon dont PythonOperator remplace son champ template_ext dans __init__ ne fonctionne pas. Les tâches ne vérifient que template_ext sur le __class__. Pour créer un PythonOperator qui récupère les fichiers de modèle SQL, il vous suffit de procéder comme suit:

class SQLTemplatedPythonOperator(PythonOperator):
    template_ext = ('.sql',)

Et ensuite, pour accéder au SQL à partir de votre tâche quand elle s'exécute:

SQLTemplatedPythonOperator(
    templates_dict={'query': 'my_template.sql'},
    params={'my_var': 'my_value'},
    python_callable=my_func,
    provide_context=True,
)

def my_func(**context):
    context['templates_dict']['query']
10
Ardan

Je ne pense pas que ce soit vraiment possible. Mais la solution de contournement suivante peut être utile:

def templated_function(ds, **kwargs):
    kwargs['ds'] = ds                                # put ds into 'context'
    task = kwargs['task']                            # get handle on task
    templ = open(kwargs['templates_dict']['file1']).read() # get template
    sql = task.render_template('', tmpl, kwargs)           # render it
    pp.pprint(sql)

J'aimerais une meilleure solution, cependant!

8
Will Fitzgerald

Récemment, je suis tombé sur le même problème et l'ai finalement résolu. La solution de @Ardan est correcte mais je souhaite simplement répéter avec une réponse plus complète avec quelques détails sur le fonctionnement d’Airflow pour les nouveaux arrivants. 

Bien sûr, vous avez d’abord besoin de l’une de celles-ci:

from airflow.operators.python_operator import PythonOperator

class SQLTemplatedPythonOperator(PythonOperator):

    # somehow ('.sql',) doesn't work but Tuple of two works...
    template_ext = ('.sql','.abcdefg')

En supposant que vous ayez un fichier de modèle SQL comme ci-dessous:

# stored at path: $AIRFLOW_HOME/sql/some.sql
select {{some_params}} from my_table;

Commencez par vous assurer que vous ajoutez votre dossier au chemin de recherche dans vos paramètres de dag.

Ne passez pas template_searchpath aux arguments, puis passez les arguments à DAG !!!! Ça ne marche pas

dag = DAG(
    dag_id= "some_name",
    default_args=args,
    schedule_interval="@once",
    template_searchpath='/Users/your_name/some_path/airflow_home/sql'
)

Ensuite, votre appel opérateur sera

SQLTemplatedPythonOperator(
        templates_dict={'query': 'some.sql'},
        op_kwargs={"args_directly_passed_to_your_function": "some_value"},
        task_id='dummy',
        params={"some_params":"some_value"},
        python_callable=your_func,
        provide_context=True,
        dag=dag,
    )

Votre fonction sera:

def your_func(args_directly_passed_to_your_function=None):
    query = context['templates_dict']['query']
    dome_some_thing(query)

Quelques explications:

  1. Airflow utilise les valeurs du contexte pour rendre votre modèle. Pour l'ajouter manuellement au contexte, vous pouvez utiliser le champ params comme ci-dessus.

  2. PythonOperator ne prend plus l'extension de fichier de modèle du champ template_ext comme @Ardan mentionné. Le code source est ici . Il ne faut que l'extension de self .__ class __. Template_ext.

  3. Le flux d'air parcourt le champ template_dict et si value.endswith (extension_fichier) == True, il restitue le modèle.

6
P. Xie

Impossible de faire fonctionner un fichier de script basé sur un modèle python (nouveau sur python). Mais un exemple avec l'opérateur bash suit, peut-être que cela peut vous donner des indices

from datetime import datetime
from airflow import DAG
from airflow.operators.bash_operator import BashOperator

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    #'start_date': airflow.utils.dates.days_ago(2),
    'email': ['[email protected]']}

dag = DAG('sr5', description='Simple tutorial DAG',
          schedule_interval='0 12 * * *',
          start_date=datetime(2017, 3, 20),
          catchup=False, #so that on scehduler restart, it doesn't try to catchup on all the missed runs
          template_searchpath=['/Users/my_name/Desktop/utils/airflow/resources'])

t1 = BashOperator(
    task_id='t1',
    depends_on_past=False,
    params={
        'ds1': 'hie'},
    bash_command="01.sh",
    dag=dag)

le script 01.sh ressemble à ce qui suit

#!/bin/sh

echo {{ ds }}
echo {{ params.ds1 }}

Cela donne un résultat comme suit lors de l'exécution du test

[2017-05-12 08: 31: 52,981] {bash_operator.py:91} INFO - Résultat:

[2017-05-12 08: 31: 52,984] {bash_operator.py:95} INFO - 2017-05-05

[2017-05-12 08: 31: 52,984] {bash_operator.py:95} INFO - hie

1
Saurabh Mishra