Python 模板在气流';s打开\u失败\u回调

Python 模板在气流';s打开\u失败\u回调,python,airflow,Python,Airflow,问题:在调用失败时使用的运算符中的模板不呈现 def报告失败(上下文): 发送电子邮件=电子邮件操作员( task\u id=“email\u失败”, 收件人=电子邮件收件人, subject=“{execution_date}}”, html_content=get_email_body()#返回“body:{{execution_date}” ) #我尝试的一个解决方案失败了:AttributeError:“EmailOperator”对象没有属性“render\u template\u f

问题:在调用失败时使用的
运算符中的模板不呈现

def报告失败(上下文): 发送电子邮件=电子邮件操作员( task\u id=“email\u失败”, 收件人=电子邮件收件人, subject=“{execution_date}}”, html_content=get_email_body()#返回“body:{{execution_date}” ) #我尝试的一个解决方案失败了:AttributeError:“EmailOperator”对象没有属性“render\u template\u fields” #这很奇怪,因为这种方法出现在EmailOperator的基础上:BaseOperator #发送\电子邮件。呈现\模板\字段(上下文) 发送电子邮件。执行(上下文) 默认参数={ “on\u failure\u callback”:报告\u失败 }
发送的电子邮件包含字面上的
{{execution\u date}}
,而不是呈现值

在这个简单的例子中,我可以使用
.format(**context)
对这些模板化字符串进行适当的格式化,但是在
context
不可用且模板化工作正常的其他地方,我可以重复使用此电子邮件发送


更大的目标是在DAG(或其子DAG)的任何步骤失败时启动此“错误处理程序”。

您这样做可能会奏效,但您肯定需要双花括号来创建jinja模板:
{execution\u date}
->
{{execution\u date}

您还应该能够使用上下文参数获取执行日期:

def报告失败(上下文): 发送电子邮件=电子邮件操作员( task\u id=“email\u失败”, 收件人=电子邮件收件人, 主题=上下文[“执行日期”], html_content=“Body:{execution_date}”。格式(execution_date=context['execution_date']) ) 您可能还会发现这一点很有用:

模板在那里不起作用的原因是,模板不是由
execute()
来呈现的,而是事先呈现的

你需要做的就是


def报告_故障(上下文):
发送电子邮件=电子邮件操作员(
task\u id=“email\u失败”,
收件人=电子邮件收件人,
subject=“{execution_date}}”,
html_content=get_email_body()#返回“body:{{execution_date}”
)
send_email.dag=上下文['dag']
send_email.start_date=send_email.dag.start_date
发送电子邮件。呈现模板字段(上下文,jinja\u env=context['dag'].get\u template\u env())
发送电子邮件。执行(上下文)

使用不同的方法多次尝试后,我发现以下解决方案有效:

解决方案A:不要使用on\u failure\u回调,而是使用trigger\u rule='all\u failed'/'one\u failed'创建任务; 这是雅各布提出的气流的松弛,它似乎工作良好。这是概念上最简单的IMHO

status_failed = SimpleHttpOperator(
    trigger_rule='all_failed', # See https://airflow.apache.org/docs/stable/concepts.html
    task_id='updateStatus',
    ...
)
email_failed = EmailOperator(
    trigger_rule='all_failed',
)

start_task >> do_the_thing >> status_success >> email_success
# Handling errors in case any job fails
email_success >> email_failed
email_success >> status_failed
示例:成功运行,将整个DAG的状态设置为“成功”,并将错误处理程序设置为跳过。

解决方案B:使用on_failure_回调并手动渲染模板 正如Ash所说的
模板不起作用的原因是,模板不是由execute()呈现的,而是事先呈现的。
我发现以下解决方案起作用:

def report_failure(context):
    send_email = EmailOperator(
        task_id="email_failed",
        start_date=datetime(2015, 12, 1), # Any date in the past, if you won't set it you will get an error
        to=emailreceipients,
        subject="{{execution_date}}",
        html_content=get_email_body() # Which returns "Body: {{execution_date}}"
    )

    # Set DAG, otherwise we will get errors
    send_email.dag = context['dag']

    # Manually render templates
    # send_email.render_template_fields(context) # Working in Airflow 1.10.6
    # send_email.html_content = send_email.render_template('', send_email.html_content, context) # Working in Airflow 1.10.4
    # Looking at codebase seems to be working in both versions
    send_email.html_content = send_email.get_template_env().from_string(send_email.html_content).render(**context)

    send_email.execute(context)
它的问题是,这些函数似乎经常更改,因此可能很难对其进行更新

解决方案C:使用
上下文
变量格式化消息 正如雅各布所建议的


当生成模板的代码与不使用上下文的操作符共享时,它不能解决我的特殊情况。尽管如此,它可能对更简单的情况有所帮助,因此我将其发布在这里。

需要帮助在on_failure_回调中呈现jinja模板电子邮件ID

使用Variable.get('email\u edw\u alert')可以很好地工作,但我不想使用Variable方法来避免命中DB

下面是Dag文件

import datetime
import os
from functools import partial
from datetime import timedelta
from airflow.models import DAG,Variable
from airflow.contrib.operators.snowflake_operator import SnowflakeOperator
from alerts.email_operator import dag_failure_email


def get_db_dag(
    *,
    dag_id,
    start_date,
    schedule_interval,
    max_taskrun,
    max_dagrun,
    proc_nm,
    load_sql
):

    default_args = {
        'owner': 'airflow',
        'start_date': start_date,
        'provide_context': True,
        'execution_timeout': timedelta(minutes=max_taskrun),
        'retries': 0,
        'retry_delay': timedelta(minutes=3),
        'retry_exponential_backoff': True,
        'email_on_retry': False,
    }


    dag = DAG(
        dag_id=dag_id,
        schedule_interval=schedule_interval,
        dagrun_timeout=timedelta(hours=max_dagrun),
        template_searchpath=tmpl_search_path,
        default_args=default_args,
        max_active_runs=1,
        catchup='{{var.value.dag_catchup}}',
        on_failure_callback=partial(dag_failure_email, config={'email_address': '{{var.value.email_edw_alert}}'}),
    )


    load_table = SnowflakeOperator(
        task_id='load_table',
        sql=load_sql,
        snowflake_conn_id=CONN_ID,
        autocommit=True,
        dag=dag,
    )

    load_table

    return dag

# ======== DAG DEFINITIONS #

edw_table_A = get_db_dag(
    dag_id='edw_table_A',
    start_date=datetime.datetime(2020, 5, 21),
    schedule_interval='0 5 * * *',
    max_taskrun=3,  # Minutes
    max_dagrun=1,  # Hours
    load_sql='recon/extract.sql',
)
下面是python代码

import logging
from airflow.utils.email import send_email
from airflow.models import Variable

logger = logging.getLogger(__name__)

TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

def dag_failure_email(context, config=None):

    config = {} if config is None else config
    task_id = context.get('task_instance').task_id
    dag_id = context.get("dag").dag_id
    execution_time = context.get("execution_date").strftime(TIME_FORMAT)
    reason = context.get("reason")

    alerting_email_address = config.get('email_address')

    dag_failure_html_body = f"""<html>
    <header><title>The following DAG has failed!</title></header>
    <body>
    <b>DAG Name</b>: {dag_id}<br/>
    <b>Task Id</b>: {task_id}<br/>
    <b>Execution Time (UTC)</b>: {execution_time}<br/>
    <b>Reason for Failure</b>: {reason}<br/>
    </body>
    </html>
    """

    try:
        if reason != 'dagrun_timeout':
            send_email(
                to=alerting_email_address,
                subject=f"Airflow alert: <DagInstance: {dag_id} - {execution_time} [failed]",
                html_content=dag_failure_html_body,
            )
    except Exception as e:
        logger.error(
            f'Error in sending email to address {alerting_email_address}: {e}',
            exc_info=True,
        )
导入日志
从afflow.utils.email导入发送电子邮件
从airflow.models导入变量
logger=logging.getLogger(_名称__)
时间\u格式=“%Y-%m-%d%H:%m:%S”
def dag_故障_电子邮件(上下文,配置=无):
config={}如果config是None-else-config
task\u id=context.get('task\u instance')。task\u id
dag_id=context.get(“dag”).dag_id
执行时间=context.get(“执行日期”).strftime(时间格式)
reason=context.get(“reason”)
警报\u email\u address=config.get('email\u address'))
dag\u失败\u html\u正文=f”“”
以下DAG已失败!
DAG名称:{DAG_id}
任务Id:{Task_Id}
执行时间(UTC):{Execution_Time}
失败原因:{Reason}
""" 尝试: 如果有理由“运行超时”: 发送电子邮件( 收件人=通知电子邮件地址,
主题=f"气流警报:我在给出这个示例时犯了错误。我使用了双大括号,但它不起作用。我不能/不想直接使用
上下文
参数,因为正如我提到的,电子邮件正文来自实用函数。我更新了示例以更清楚地说明问题。我不完全确定
on\u failure\u回调
是用于触发的更多运算符,这可能是模板设置不起作用的原因。我建议使用以下方法解决此问题:
格式
上下文
,或者将此EmailOperator添加到dag中,使用触发器规则
一个失败
发送电子邮件。呈现模板字段(上下文)
不起作用,因为我浏览了1.10.6版,而Docker中有1.10.4版。我收到错误
操作员尚未分配给DAG
。添加了
send_email.DAG=context['DAG']
,然后得到
DAG没有设置开始日期
(或类似).我正在尝试不同的方法..我还需要向EmailOperator添加
start\u date=datetime(2015,12,1)
 try:
        if reason != 'dagrun_timeout':
            send_email = EmailOperator(
               to=alerting_email_address,
               task_id='email_task',
               subject=f"Airflow alert: <DagInstance: {dag_id} - {execution_time} [failed]",
               params={'content1': 'random'},
               html_content=dag_failure_html_body,
           )
            send_email.dag = context['dag']
            #send_email.to = send_email.get_template_env().from_string(send_email.to).render(**context)
            send_email.to = send_email.render_template(alerting_email_address, send_email.to, context)
            send_email.execute(context)
    except Exception as e:
        logger.error(
            f'Error in sending email to address {alerting_email_address}: {e}',
            exc_info=True,
        )