之前公司的项目部署主要使用的是 ansible 编排,说到 ansible 就不得不提到强大的 jinja 语法了。而后来公司又让各个服务把部署方式改成 SDK 安装的方式,这个转变就引发了一些重复利用文件的问题,最后的解决办法就是使用 yaml 和 jinja2 将原本属于 ansible 的模板文件充分利用起来了。这篇文章就来分享一下我在工作中使用到的 jinja 用法。

yaml 库的简单使用

YAML 是现在比较流行(我觉得用最流行也不为过)的配置文件格式,它相较于 XML 格式更加简洁,同时又比 JSON 直观,所以成了各个语言的微服务必备配置文件。Python 有个 yaml 库可以用来操作 yaml 格式的文件,一般读取文件的操作使用比较多。

下面这个就是最简单地读取 yaml 格式的文件的方式

from yaml import safe_load

with open(filename, 'r') as f:
    vars_string = f.read()
base_vars = safe_load(vars_string)

使用 safe_load() 函数可以将读取到的 yaml 文件的字符串转换成字典格式,方便后续 jinja 使用。

yaml 与 jinja2 结合使用

现在有一个场景,就是需要把 ansible 的配置文件(一般都是一个名为 all 的 yaml 格式的文件)读取成字典进行调用。现在这个配置文件部分内容如下:

date: 2019-11-06
pkg:
  python:
    version: 3.6.8
    date: "{{ date }}"
  django:
    version: "{% if pkg.python.version|first == '2' %}1.8{% else %}2.2.6{% endif %}"

这里可以看到,虽然这个只是一个 yaml 文件,但是里面是含有 jinja 语法的,所以如果单纯的使用上面的 safe_load() 函数,会发现得到的字典里面是包含 jinja 语法的,这当然不能被使用。所以,需要在提取到文件的信息之后进行处理,也就是使用 jinja2 对 yaml 文件进行渲染。

具体的代码可以看下面这个函数:

from yaml import safe_load
from jinja2 import Template
def get_vars_from_file(filename):
    with open(filename, 'r') as f:
        vars_string = f.read()
    # 读取初始的配置文件,并转换成字典
    base_vars = safe_load(vars_string)
    # 使用当前配置参数渲染自己本身,把配置中jinja语法渲染成实际值
    vars = Template(vars_string).render(base_vars)
    return vars

if __name__ == '__main__':
    fn = 'vars.yml'
    vars = get_vars_from_file(fn)
    print(vars)

这个函数做的事情不仅仅是读取当前的配置文件,而是在读取到配置文件之后,使用自身来渲染自身,从而达到把自身包含的 jinja 语法去掉,这个里面使用到的 jinja2 的用法很基础,就是一个 Template 类用来实例化一个待渲染的对象,然后使用 render() 方法使用指定的参数得到渲染结果。最终输出如下:

date: 2019-11-06
pkg:
  python:
    version: 3.6.8
    date: "2019-11-06"
  django:
    version: "2.2.6"

这里可以看到,经过一轮自己渲染自己的过程之后,配置文件已经不再包含 jinja 语法了,这个时候可以继续使用 safe_load() 方法转换成字典以备后续使用。这个文件里面涉及到的 jinja 语法并不复杂,我这里也不对 jinja 语法做太多举例,自己可以根据需要去查官方文档。

jinja2 高级用法

上面使用 Template 类可以很方便地对模板进行渲染,但是很多时候我们需要做的不是简单的渲染,而是更多复杂的事情,所以这里就需要用到 jinja2 的高级类 Environment。使用过 jinja 语法(其实 django 的模板语法跟 jinja 语法也有很多一样的)都知道语法里面有几个基本的标签,比如 {{}} 表示的变量标签,{%%} 表示的块标签,{##}表示的是注释标签,jinja 默认会把这些标签里面的内容进行渲染,但是有时候我们可能不想要渲染这些语法,这个时候,Environment 类就可以发挥作用了,它可以在初始化的时候自定义上述的几种标签格式,进而做到根据自定义的语法标签去渲染模板。

下面是一个简单的应用场景,需要将某个目录下面的 html 文件渲染:

from yaml import safe_load
from jinja2 import Template, Environment, FileSystemLoader

def get_vars_from_file(filename):
    with open(filename, 'r') as f:
        vars_string = f.read()
    # 读取初始的配置文件,并转换成字典
    base_vars = safe_load(vars_string)
    # 使用当前配置参数渲染自己本身,把配置中jinja语法渲染成实际值
    vars = Template(vars_string).render(base_vars)
    return vars

def test_render(vars, filename):
    load = FileSystemLoader('templates')
    env = Environment(loader=load)
    template = env.get_template(filename)
    result = template.render(vars)
    print(result)

if __name__ == '__main__':
    fn = 'vars.yml'
    vars = get_vars_from_file(fn)
    test_render(safe_load(vars), 'base.html')

直接看 test_render() 函数的内容就行了,一般 Environment, FileSystemLoader 两个类是一起使用的,后者是用来加载待渲染文件的类,同类型的类还有模块加载器,这里是目录加载器,就是制定需要渲染的目录,参数可以是单个目录,也可以是一个目录列表。get_template() 可以把需要渲染的文件实例化,然后就可以进行渲染了。我上面的代码并没有对 jinja2 默认的语法标签做替换,因为只是一个简单的例子。

下面是待渲染的文件 templates\base.html 的内容:

<div>
    <p>today is {{ date }}</p>
    <p>the python is python{{ pkg.python.version|first }}</p>
</div>

渲染结果如下:

<div>
    <p>today is 2019-11-06</p>
    <p>the python is python3</p>
</div>

总结

大部分情况下,使用 yaml 库可以直接读取 YAML 格式的配置文件并转换成字典使用;大部分情况下,使用 jinja2.template 就可以完成一个简单的 jinja 模板的渲染。当 yaml 和 jinja2 一起使用的时候,可以触发“强强联手”羁绊,非常强大。

版权声明:如无特殊说明,文章均为本站原创,转载请注明出处

本文链接:https://tendcode.com/article/yaml_and_jinja2/