介绍
这几天在编写一个数据驱动测试框架,其中一个核心模块是根据数据输入,自动转换为测试用例代码。测试数据格式固定,对应的用例代码结构也相对稳定。基于此场景,联想到web页面根据数据从template渲染页面,决定使用类似的方式,采用模版库动态生成测试用例代码。
Python库有两个常用的模版库:Jinja2和Mako。两个库功能都很齐全强大,Jinja2广泛用于各种语言环境如Java/Go,Mako主要用于Python语言环境。
Mako模板从一个包含各种类型的内容的文本流解析得到,包括XML、HTML、email文本等。模板还可以包含Mako指令,用来表示变量和表达式替换、控制结构、服务器端注释、整块Python代码,还有用来提供额外功能的各种标签。所有这些结构都被编译为实际的Python代码。
Mako能直接嵌套Python代码,更贴近python代码风格,项目使用的也是python语言环境,使用起来更加方便,网上也有说mako库解析速度更快,我们最终选择了mako库。
使用方式
下面总结一下mako使用方式。
使用Template
通过from mako.template import Template引入mako模板。
1)基本用法
from mako.template import Template
t = Template("Hello,${name}")
print(t.render(name = 'world'))
输出:
Hello,world
2)导入模版文件
Template函数还有一些常用的参数:
- filename:指定从文件中加载模板。最好用相对路径,否则缓存文件会包含在很长的路径下
- module_directory:缓存目录。从文件中加载模板,可以指定module_directory生成.py文件缓存,提高加载速度。
例如:
新建一个模版文件demo.tmpl,保存以下内容:
Hello,${
name}
使用模版代码:
from mako.template import Template
t = Template(filename="demo.tmpl", module_directory="./")
print(t.render(name='world'))
代码执行以后,当前目录下会生成一个以模版文件名,再加上后缀“.py”的缓存文件。
rend()方法会创建一个Context对象,该对象会存储所有render()方法传递给模版的变量,并且会保存为buffer用于捕获output输出。
我们也可以自定义Context对象,然后用 render_context()方法渲染。
from mako.template import Template
from mako.runtime import Context
from io import StringIO
mytemplate = Template("hello, ${name}")
buf = StringIO()
ctx = Context(buf, name="python")
mytemplate.render_context(ctx)
print(buf.getvalue())
使用TemplateLookup
实际项目中,通常会有多个模版文件,组合渲染一个页面。这种情况下,我们可以使用TemplateLookup,直接从指定的目录导入多个模版文件。
from mako.lookup import TemplateLookup
lookup = TemplateLookup(directories=['./templates'], module_directory='./', collection_size=500, filesystem_checks=True)
"""
# 使用templateLookup,可以方便的设置模板目录,当模板中include别的模板时可以只写模板名
# collection_size设置加载到内存中的模板上限
# filesystem_checks为True会在每次加载模板缓存判断模板文件是否有改动,会重新编译。
"""
def serve_template(templatename, **kwargs):
mytemplate = lookup.get_template(templatename) #get_template()方法根据文件名,获取指定的template
print(mytemplate.render(**kwargs))
serve_template("index.html", name="python")
异常处理
异常主要出现在查找并解析模版文件,以及渲染模版两个场合。渲染阶段的异常主要是python自带的异常。mako封装了异常类处理模版解析过程中的异常堆栈,将堆栈数据格式输出化为text或者html格式。
text_error_template() 输出text格式异常堆栈;html_error_template() 输出html格式异常堆栈。这两个方法本质上都是调用sys.exc_info()获取程序最近抛出的异常。
- text模版异常
from mako import exceptions
try:
template = lookup.get_template(uri)
print(template.render())
except:
print(exceptions.text_error_template().render())
- html格式模版异常
from mako import exceptions
try:
template = lookup.get_template(uri)
print(template.render())
except:
print(exceptions.html_error_template().render())
- 自定义异常:
exceptions类底层使用的RichTraceback对象。该对象也可以用来自定义异常。
from mako.exceptions import RichTraceback
try:
template = lookup.get_template(uri)
print(template.render())
except:
traceback = RichTraceback()
for (filename, lineno, function, line) in traceback.traceback:
print("File %s, line %s, in %s" % (filename, lineno, function))
print(line, "\n")
print("%s: %s" % (str(traceback.error.__class__.__name__), traceback.error
语法
表达式替换
- 最简单的表达式是变量替换。语法为 ${var}
Hello, this is my name: ${var}
上面的例子会把 var 的字符串表示输出到模板的输出流。 var通常来自于传递给模板渲染函数的 Context 。如果没有传入x 给模板,并且也没有本地赋值,那么就等于一个特殊的值 UNDEFINED 。
例如,模版文件demo.tmpl包含以下内容:
Hello, this is my name: ${
name}
print(${
version})
但是render()方法没有传递version参数时,会抛出异常“NameError: Undefined”
from mako.template import Template
t = Template(filename="./templates/demo.tmpl", module_directory="./")
print(t.render(name='world'))
运行结果:
Traceback (most recent call last):
File "/Users/bruce.xu/mako/basic_usage.py", line 19, in <module>
print(t.render(name='world'))
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mako/template.py", line 476, in render
return runtime._render(self, self.callable_, args, data)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mako/runtime.py", line 883, in _render
**_kwargs_for_callable(callable_, data)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mako/runtime.py", line 920, in _render_context
_exec_template(inherit