HiveOperator 中 hql 模板路径解析失败的原因分析

本文由「大千AI助手」原创发布,专注用真话讲AI,回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我,一起撕掉过度包装,学习真实的AI技术!

问题描述

  • HiveOperator 中以类似 ../xxx.hql/yyy/xxx.hql 的方式设置参数 hql 时, 任务执行时报错找不到对应的模板
    • 比如 TemplateNotFound 异常

本文由「大千AI助手」原创发布,专注用真话讲AI,回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我,一起撕掉过度包装,学习真实的AI技术!

往期文章推荐:

问题分析

  • 1.HiveOperator 的参数 hql 用来配置运行的 hive sql 或者 包含hive sql的jinja2模板文件
  • 2.并且参数 hql 被设置为可以被 jinja2 引擎渲染的
  • 3.并且参数 hql 被设置为如果以 .hql.sql 结尾,那么 hql 被整体当作 模板文件名 来通过 jinja2 渲染,其余情况都被当作 hive sql字符串 直接渲染
python 复制代码
# 源码 HiveOperator 中的部分参数设置
template_fields = ('hql', 'schema', 'hive_cli_conn_id', 'mapred_queue',
                'hiveconfs', 'mapred_job_name', 'mapred_queue_priority')
template_ext = ('.hql', '.sql',)


# 源码 airflow/models/baseoperator.py
class BaseOperator(LoggingMixin):
    ...

    def render_template(self, content, context, jinja_env=None, seen_oids=None):
        if not jinja_env:
            jinja_env = self.get_template_env()

        if isinstance(content, six.string_types):
            if any(content.endswith(ext) for ext in self.template_ext):
                # Content contains a filepath
                return jinja_env.get_template(content).render(**context)
            else:
                return jinja_env.from_string(content).render(**context)
    ...
  • 4.而Jinja2 在模板加载阶段会对路径进行安全校验,明确禁止目录回溯和越权访问
    • jinja2 在通过 FileSystemLoader 加载本地模板文件时会对路径进行特殊检查和处理
      • 如果模板文件路径中包含 .. 则直接抛出异常 TemplateNotFound 表示找不到模板文件
        • 通过条件 piece == os.path.pardir 来判断的 (os.path.pardir 代表父目录,取值为 ..)
      • 并且模板都是在 searchpath 路径下查找的,如果用了绝对路径,那肯定也是找不到的(除非 searchpath = '/')
python 复制代码
# 源码路径: jinja2/loaders.py
def split_template_path(template: str) -> t.List[str]:
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            os.path.sep in piece
            or (os.path.altsep and os.path.altsep in piece)
            or piece == os.path.pardir
        ):
            raise TemplateNotFound(template)
        elif piece and piece != ".":
            pieces.append(piece)
    return pieces


class FileSystemLoader(BaseLoader):
    ...

    def get_source(
        self, environment: "Environment", template: str
    ) -> t.Tuple[str, str, t.Callable[[], bool]]:
        pieces = split_template_path(template)

        for searchpath in self.searchpath:
            # Use posixpath even on Windows to avoid "drive:" or UNC
            # segments breaking out of the search directory.
            filename = posixpath.join(searchpath, *pieces)

            if os.path.isfile(filename):
                break
        else:
            plural = "path" if len(self.searchpath) == 1 else "paths"
            paths_str = ", ".join(repr(p) for p in self.searchpath)
            raise TemplateNotFound(
                template,
                f"{template!r} not found in search {plural}: {paths_str}",
            )
        ...
  • 5.而使用的 searchpath 包括 任务所在的dag的定义的文件的路径创建dag时传的参数 template_searchpath
    • self.folder 就是定义该任务的 dag 的文件所在的父目录
      • 一般在任务执行日志中能看到 '-sd', 'DAGS_FOLDER/xxx.py'
python 复制代码
# 源码 airflow/models/dag.py
class DAG(BaseDag, LoggingMixin):
    ...

    @property
    def folder(self):
        """Folder location of where the DAG object is instantiated."""
        return os.path.dirname(self.full_filepath)
    ...

    def get_template_env(self):  # type: () -> jinja2.Environment
        """Build a Jinja2 environment."""

        # Collect directories to search for template files
        searchpath = [self.folder]
        if self.template_searchpath:
            searchpath += self.template_searchpath

        # Default values (for backward compatibility)
        jinja_env_options = {
            'loader': jinja2.FileSystemLoader(searchpath),
            'undefined': self.template_undefined or jinja2.Undefined,
            'extensions': ["jinja2.ext.do"],
            'cache_size': 0
        }
        if self.jinja_environment_kwargs:
            jinja_env_options.update(self.jinja_environment_kwargs)

        env = jinja2.Environment(**jinja_env_options)  # type: ignore

        # Add any user defined items. Safe to edit globals as long as no templates are rendered yet.
        # http://jinja.pocoo.org/docs/2.10/api/#jinja2.Environment.globals
        if self.user_defined_macros:
            env.globals.update(self.user_defined_macros)
        if self.user_defined_filters:
            env.filters.update(self.user_defined_filters)

        return env

    ...

解决方案

  • 方案1.使用 相对路径 和 与对应的 DAG 定义文件同级或子目录存放模板文件
  • 方案2.通过参数 template_searchpath 来设置当前 DAG 特有的模板搜索目录
    • 创建 DAG 时的初始化参数
  • 方案3.通过 HiveOperator 执行的 hql 模板文件 禁止用绝对路径.. 的路径

本文由「大千AI助手」原创发布,专注用真话讲AI,回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我,一起撕掉过度包装,学习真实的AI技术!

相关推荐
小北方城市网2 小时前
第 4 课:前端工程化进阶 ——Vue 核心语法 + 组件化开发(前端能力质的飞跃)
大数据·开发语言·数据库·python·状态模式·数据库架构
㳺三才人子2 小时前
初探 Python + Django
开发语言·python·django
Hello.Reader2 小时前
Hive Dialect 的查询能力支持哪些 HiveQL 子集,怎么写、怎么跑
数据仓库·hive·hadoop
寻星探路2 小时前
网络原理全景图:从通信起源到 TCP/IP 体系架构深度拆解
java·网络·c++·python·tcp/ip·http·架构
子一!!2 小时前
MySQL==表的结构操作1
android·python·adb
清水白石0082 小时前
动态规划中的记忆化与缓存:原理、差异与 Python 实战指南
python·缓存·动态规划
无垠的广袤2 小时前
【上海晶珩睿莓 1 单板计算机】物联网环境监测终端
linux·python·嵌入式硬件·物联网·mqtt·home assistant
Feibo20112 小时前
R制作研究报告
python
axinawang2 小时前
浙江省高中信息技术(Python)--进阶刷题(选修)
python·浙江省高中信息技术