跟python一样,在mako中,也可以用def来定义一个函数。不过在功能上,mako中的def比python稍微强大一点。
一、函数定义
在mako中,使用<%def>来定义一个函数。注意,这里函数名需要表示成name参数的值。
<%def name="hello()">
hello world
</%def>
定义完函数之后,就可以在模板中直接调用该函数:
call def: ${hello()}
通过这种方式定义的函数是mako的顶层函数(top level def),它可以在该模板任意地方使用,也可以通过namespace在其他模板中使用。
二、函数定义的背后
在mako的运行过程中,所有模板都会生成对应的py文件,浏览这些文件可以很好的理解mako背后的运行机制。在mako中,每一个模板对应的py文件中,都会有一个render_body函数,它是模板的主路口,模板render就是调用这个函数完成整个模板的渲染的。如果在模板中,定义了顶层函数,那么还会生成一个类似render_hello这样的函数,这里hello是具体的函数名。我们上面的例子在编译成py文件之后,就会得到如下代码:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
def hello(): # hello函数,这个函数只完成对render_hello的一个调用,并将上下文作为参数传递下去
return render_hello(context.locals_(__M_locals))
__M_writer = context.writer()
# SOURCE LINE 3
__M_writer(u'\n\ncall def: ')
# SOURCE LINE 5
__M_writer(unicode(hello())) # 调用hello函数
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_hello(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'\n hello world\n’) # 显示hello world
return ''
finally:
context.caller_stack._pop_frame()
三、参数的传递
其实,在mako中,说起函数参数可分为def定义的参数和传入到函数的数据参数两种,就像上面提到的函数名name=“hello()”,其实是def的参数,def还有几个类似的参数,我们后面会深入讨论;而如果我们需要往这个函数中传入一些参数量,则也需要写在name这个变量名中,如:
${account(accountname='john')}
<%def name="account(accountname, type='regular')">
account name: ${accountname}, type: ${type}
</%def>
上述代码定义了一个函数account,该函数带两个参数accountname、type,其中type带有默认值。这些参数的使用方式跟python中函数的定义都是一致的。
使用了函数参数之后,编译之后生成的代码片段如下:
def account(accountname,type='regular'):
return render_account(context.locals_(__M_locals),accountname,type)
因此,可以看到,mako仅仅是将这些参数原原本本的转到了python里面而已。因此,所有跟python函数参数相关的规则都适用于mako函数参数。
四、变量的传递
在mako中,还会涉及到render传入的参数变量、模板中定义的变量等。这些变量的处理方式跟函数参数是不一样的。下面我们来看个例子:
Hello there ${username}, how are ya. Lets see what your account says:
<%
module_local_var = 'module_local_var'
%>
${account(accountname='john')}
<%def name="account(accountname)">
<%
account_local_var = 'account_local_var'
%>
Account name for ${username}: ${accountname}
module_local_var = ${module_local_var}
account_local_var = ${account_local_var}
</%def>
这段程序有四种变量:
* username,从render(或framework)传入,放入了context中,因此,可以直接在模板中引用。
* module_local_var ,是在模板中定义的变量
* account_local_var ,是在一个模板函数中定义的变量
*accountname,是函数的参数变量,在函数调用的时候指定。
下面我们来看一下编译生成的py文件:
def render_body(context,**pageargs):
__M_caller = context.caller_stack._push_frame()
try:
__M_locals = __M_dict_builtin(pageargs=pageargs)
username = context.get('username', UNDEFINED) # username是从context中取得的,如果没有定义,则默认使用UNDEFINED
def account(accountname):
return render_account(context.locals_(__M_locals),accountname) # accountname 作为函数参数传入
__M_writer = context.writer()
# SOURCE LINE 1
__M_writer(u'Hello there ')
__M_writer(unicode(username))
__M_writer(u', how are ya. Lets see what your account says:\n')
# SOURCE LINE 2
module_local_var = ‘module_local_var’ # 在模板中定义的变量,现在是render_body函数的变量
__M_locals_builtin_stored = __M_locals_builtin()
__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['module_local_var'] if __M_key in __M_locals_builtin_stored])) # 将module_local_var 放入__M_locals中,这样render_account函数就能从context中取得该值
# SOURCE LINE 4
__M_writer(u'\n')
# SOURCE LINE 5
__M_writer(unicode(account(accountname='john'))) # 函数调用,传入参数值
__M_writer(u'\n\n')
# SOURCE LINE 14
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
def render_account(context,accountname):
__M_caller = context.caller_stack._push_frame()
try:
username = context.get('username', UNDEFINED) # 在函数中再次取得username,两个函数之间通过context传递信息
module_local_var = context.get('module_local_var', UNDEFINED) # 从context中取得module_local_var,这也是模板级别的变量传递的途径
__M_writer = context.writer()
# SOURCE LINE 7
__M_writer(u'\n')
# SOURCE LINE 8
account_local_var = ‘account_local_var’ # 函数内部定义的变量,不用变化
# SOURCE LINE 10
__M_writer(u'\n Account name for ')
# SOURCE LINE 11
__M_writer(unicode(username))
__M_writer(u': ')
__M_writer(unicode(accountname))
__M_writer(u'
\n module_local_var = ')
# SOURCE LINE 12
__M_writer(unicode(module_local_var))
__M_writer(u'
\n account_local_var = ')
# SOURCE LINE 13
__M_writer(unicode(account_local_var))
__M_writer(u'
\n')
return ''
finally:
context.caller_stack._pop_frame()
由上面的代码,我们可以知道,render传入的参数、模板中定义的变量都是需要经过context传递的。
再来看一个错误使用参数的例子
<%
module_local_var = 'module_local_var'
%>
${account()}
<%def name="account()">
module_local_var = ${module_local_var}
<%
module_local_var = 'new_module_local_var'
%>
</%def>
这里的区别是,我们在函数中重新赋值了module_local_var,并且在赋值之前使用了该变量,我们知道这在python中是要出错的,因此在mako中也必须要出错,要不就混淆了。我们看看生成的代码:
def render_account(context):
__M_caller = context.caller_stack._push_frame()
try:
__M_writer = context.writer()
# SOURCE LINE 6
__M_writer(u'\n module_local_var = ')
# SOURCE LINE 7
__M_writer(unicode(module_local_var))
__M_writer(u'
\n')
# SOURCE LINE 8
module_local_var = 'new_module_local_var'
# SOURCE LINE 10
__M_writer(u'\n')
return ''
finally:
context.caller_stack._pop_frame()
从这段代码可以看出,只要在函数中重新赋值了module_local_var 这个变量,那么这个变量就是一个函数内的局部变量,就没有再从context取变量这个步骤了,也因此,这段代码显然就报错了。
Saturday, November 30, 2013
Thursday, November 28, 2013
mako二三事 -- 与Babel的结合
为了支持国际化,在python程序中,经常需要使用Babel将语言相关的字符串抽取出来,形成pot文件。而在mako模板文件中,有大量的文本性内容需要国际化处理,因此,mako必须要支持Babel的抽取功能。
一、mako字符串的简单抽取
在mako中,使用了一个名为mako.ext.babelplugin的Babel抽取插件,正确安装Babel、mako后,就可以使用pybabel命令进行字符串抽取。
首先,我们创建一个a.html文件,内容为:
Name:
${_('Francois Pinard')}
再创建一个babel.cfg文件,作为Babel 的配置文件:
[mako: **.html]
input_encoding = utf-8
然后运行命令
pybabel extract -F babel.cfg .
即可在console输出:
extracting messages from a.html (input_encoding="utf-8")
# Translations template for PROJECT.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR, 2013.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-11-26 16:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: a.html:3
msgid "Francois Pinard"
msgstr ""
这个就是我们通常使用的pot文件,有了pot文件,我们就可以很方便的按照国际化命令生成po文件,mo文件。
在a.html中,我们可以看到的一个明显的变化是:Francois Pinard这段文本被改写成了${_('Francois Pinard’)},mako的Babel插件能够识别这种格式并抽取到pot文件中,而普通的文本name:则不会。
Babel进行抽取的时候,需要指定一个配置文件,这里我们做了一个简单的配置文件babel.cfg。在该文件中,**.html表示本目录以及子目录所有html文件,前面的mako表示这些文件均有mako的Babel插件来解析。input_encoding为模板的编码格式,这个跟模板载入时用的encoding是一致的。
二、对抽取字符串做注释
有时候,为了能够清楚分辨抽取字符串,可以对这些字符串做一个注释。
首先,我们修改a.html为:
Name:
## TRANSLATORS: This is a proper name. See the gettext
## manual, section Names.
${_('Francois Pinard')}
这里要注意的是,这个注释必须直接放在抽取字符串之前,中间不能有其他多余字符串。
然后,运行
pybabel extract -F babel.cfg -c "TRANSLATORS:” .
可以得到:
#. TRANSLATORS: This is a proper name. See the gettext
#. manual, section Names.
#: a.html:5
msgid "Francois Pinard"
msgstr ""
从上面结果可以看到,在pot文件中,将html文件里面对这个字符串的注释也抽取到了该文件中。
三、pyramid项目的支持
通常的,我们不会简单的对一个文件进行国际化,而是对一个使用了特定framework的项目进行国际化。比如使用了pyramid框架,通过pcreate生成的项目中,会有setup.py和setup.cfg两个文件,为了支持Babel抽取,需要在setup.py中加入mako配置(setup中加两个参数):
package_data={'mypyramid': ['locale/*/LC_MESSAGES/*.mo']},
message_extractors = { 'mypyramid': [
('**.py', 'lingua_python', None ),
('templates/**.pt', 'lingua_xml', None ),
('templates/**.html', 'mako', None),
('static/**', 'ignore', None),
]},
然后在mypyramid目录下建立locale文件夹即可。这里的'templates/**.html', 'mako', None含义跟上面的配置文件含义一致。
另外,在setup.cfg中,有国际化字符串抽取、编译、更新的系列配置,如果有需要,可以调整这些参数。
[compile_catalog]
directory = mypyramid/locale
domain = MyPyramid
statistics = true
[extract_messages]
add_comments = TRANSLATORS:
output_file = mypyramid/locale/MyPyramid.pot
width = 80
[init_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
[update_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
previous = true
在pyramid项目中,为了支持国际化,还需要从python程序中将_这个函数压入到context中,如下:
tsf = TranslationStringFactory('MyPyramid’)
@subscriber(BeforeRender)
def add_renderer_globals(event):
def auto_translate(string, domain=None, mapping=None):
return localizer.translate(tsf(string), domain=domain, mapping=mapping)
request = event.get('request')
if request:
event['_'] = auto_translate
一、mako字符串的简单抽取
在mako中,使用了一个名为mako.ext.babelplugin的Babel抽取插件,正确安装Babel、mako后,就可以使用pybabel命令进行字符串抽取。
首先,我们创建一个a.html文件,内容为:
Name:
${_('Francois Pinard')}
再创建一个babel.cfg文件,作为Babel 的配置文件:
[mako: **.html]
input_encoding = utf-8
然后运行命令
pybabel extract -F babel.cfg .
即可在console输出:
extracting messages from a.html (input_encoding="utf-8")
# Translations template for PROJECT.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-11-26 16:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
#: a.html:3
msgid "Francois Pinard"
msgstr ""
这个就是我们通常使用的pot文件,有了pot文件,我们就可以很方便的按照国际化命令生成po文件,mo文件。
在a.html中,我们可以看到的一个明显的变化是:Francois Pinard这段文本被改写成了${_('Francois Pinard’)},mako的Babel插件能够识别这种格式并抽取到pot文件中,而普通的文本name:则不会。
Babel进行抽取的时候,需要指定一个配置文件,这里我们做了一个简单的配置文件babel.cfg。在该文件中,**.html表示本目录以及子目录所有html文件,前面的mako表示这些文件均有mako的Babel插件来解析。input_encoding为模板的编码格式,这个跟模板载入时用的encoding是一致的。
二、对抽取字符串做注释
有时候,为了能够清楚分辨抽取字符串,可以对这些字符串做一个注释。
首先,我们修改a.html为:
Name:
## TRANSLATORS: This is a proper name. See the gettext
## manual, section Names.
${_('Francois Pinard')}
这里要注意的是,这个注释必须直接放在抽取字符串之前,中间不能有其他多余字符串。
然后,运行
pybabel extract -F babel.cfg -c "TRANSLATORS:” .
可以得到:
#. TRANSLATORS: This is a proper name. See the gettext
#. manual, section Names.
#: a.html:5
msgid "Francois Pinard"
msgstr ""
从上面结果可以看到,在pot文件中,将html文件里面对这个字符串的注释也抽取到了该文件中。
三、pyramid项目的支持
通常的,我们不会简单的对一个文件进行国际化,而是对一个使用了特定framework的项目进行国际化。比如使用了pyramid框架,通过pcreate生成的项目中,会有setup.py和setup.cfg两个文件,为了支持Babel抽取,需要在setup.py中加入mako配置(setup中加两个参数):
package_data={'mypyramid': ['locale/*/LC_MESSAGES/*.mo']},
message_extractors = { 'mypyramid': [
('**.py', 'lingua_python', None ),
('templates/**.pt', 'lingua_xml', None ),
('templates/**.html', 'mako', None),
('static/**', 'ignore', None),
]},
然后在mypyramid目录下建立locale文件夹即可。这里的'templates/**.html', 'mako', None含义跟上面的配置文件含义一致。
另外,在setup.cfg中,有国际化字符串抽取、编译、更新的系列配置,如果有需要,可以调整这些参数。
[compile_catalog]
directory = mypyramid/locale
domain = MyPyramid
statistics = true
[extract_messages]
add_comments = TRANSLATORS:
output_file = mypyramid/locale/MyPyramid.pot
width = 80
[init_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
[update_catalog]
domain = MyPyramid
input_file = mypyramid/locale/MyPyramid.pot
output_dir = mypyramid/locale
previous = true
在pyramid项目中,为了支持国际化,还需要从python程序中将_这个函数压入到context中,如下:
tsf = TranslationStringFactory('MyPyramid’)
@subscriber(BeforeRender)
def add_renderer_globals(event):
def auto_translate(string, domain=None, mapping=None):
return localizer.translate(tsf(string), domain=domain, mapping=mapping)
request = event.get('request')
if request:
event['_'] = auto_translate
Tuesday, November 26, 2013
mako二三事 -- run_wsgi
mako源码的example目录里有一个run_wsgi.py文件,是一个简单的的wsgi程序,它的主要功能就是以templates或htdocs目录作为根目录,启动一个简单的wsgi服务,该服务读入GET或POST中的参数,以及PATH_INFO作为模板文件名,然后将GET/POST的参数均放入模板context中进行模板渲染,因此,该功能可以在没有程序支持的情况下单独调试模板。
不过该程序有两个小问题:
1. 32行的 return [template.render(**d)] 最好改成return [template.render_unicode(**d).encode(‘utf-8’)],然后以utf-8格式输出。有些场景下,不转码会报错。
2. 对于非html文件,它的是读取run_wsgi.py同级目录的,这在项目中一般不合适,因此
u = re.sub(r'^\/+', '', uri)
filename = os.path.join(root, u)
这里需要看具体项目加上静态文件的目录。
在运行python run_wsgi.py之后,该程序会监听8000端口。因此,可以在浏览器中使用http://http://127.0.0.1:8000/d.html 查看d.html模板。
如果模板中有参数,就可以在链接中加入需要的参数值。如下为d.html模板:
test ${a}
则查看http://127.0.0.1:8000/d.html?a=mako,则会显示test mako
如果模板中有数组参数,如:
test ${a}
% for i, value in enumerate(names):
${i+1}. ${value}
% endfor
则可以使用链接http://127.0.0.1:8000/d.html?a=mako&names=tom&names=cat 进行查看,显示如下内容:
test mako
1. tom
2. cat
由此可以看出,通过url中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。
不过该程序有两个小问题:
1. 32行的 return [template.render(**d)] 最好改成return [template.render_unicode(**d).encode(‘utf-8’)],然后以utf-8格式输出。有些场景下,不转码会报错。
2. 对于非html文件,它的是读取run_wsgi.py同级目录的,这在项目中一般不合适,因此
u = re.sub(r'^\/+', '', uri)
filename = os.path.join(root, u)
这里需要看具体项目加上静态文件的目录。
在运行python run_wsgi.py之后,该程序会监听8000端口。因此,可以在浏览器中使用http://http://127.0.0.1:8000/d.html 查看d.html模板。
如果模板中有参数,就可以在链接中加入需要的参数值。如下为d.html模板:
test ${a}
则查看http://127.0.0.1:8000/d.html?a=mako,则会显示test mako
如果模板中有数组参数,如:
test ${a}
% for i, value in enumerate(names):
${i+1}. ${value}
% endfor
则可以使用链接http://127.0.0.1:8000/d.html?a=mako&names=tom&names=cat 进行查看,显示如下内容:
test mako
1. tom
2. cat
由此可以看出,通过url中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。
Sunday, November 24, 2013
mako二三事 -- Exceptions
mako模板运行前,会被编译成py文件,因此一旦有模板中有问题,python抛出的出错信息(如文件名、行号、出错代码等)都是针对该py文件的,因此mako需要将这些Exceptions信息转换成模板对应的信息。
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
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)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
如python抛出的exception内容为:
File "/tmp/basic.html.py", line 49, in render_body
__M_writer(unicode(no1_value))
mako转换过之后的exception内容为:
File "/dir1/basic.html", line 12, in render_body
None value=${no1_value}
mako.exceptions是mako用于处理异常的主要代码,主要函数包括:
一、text_error_template
text_error_template用于输出文本格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.text_error_template().render()
二、html_error_template
html_error_template用于输出HTML格式的异常信息。例如:
from mako import exceptions
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template("basic.html")
print mytemplate.render()
except:
print exceptions.html_error_template().render()
html_error_template函数带 full、css两个参数,默认都是True。
full参数表示是否生成完整的HTML页面结构,即带等信息。
css参数表示是否生成自带的CSS信息。
通过指定这两个参数,可以将异常页面更好的融合到应用系统中。
Template类、TemplateLookup类都有一个format_exceptions,置为True,则mako默认将所有exception都转出html格式,即便是用text_error_template输出。
三、RichTraceback
mako生成上述出错信息的底层信息类就是RichTraceback,可以通过RichTraceback生成用户自定义的出错信息格式。from mako.exceptions import RichTraceback
try:
mylookup = TemplateLookup(directories=['/dir1' ], input_encoding="utf-8", module_directory='/tmp')
template = mylookup.get_template("basic.html")
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)
traceback.traceback为调用栈列表。
traceback.errorname为exception名称
traceback.lineno为模板行数
traceback.message为出错信息
mako二三事 -- 模板载入与encoding
mako是python中比较流行的一个模板引擎,网络上也有很多相关的介绍,因此这个系列文章主要关注mako中一些平常不太注意的内容。
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
一、字符串直接渲染
mako可以直接对一段字符串进行渲染,不过这没有多少实际意义,直接用用%或字符串拼接即可,基本用于测试演示用途。
from mako.template import Template
mytemplate = Template("hello, ${name}!")
print mytemplate.render(name="jack")
如果字符串中包含了中文等内容,需要使用unicode类型:
mytemplate = Template(u"你好, ${name}!")
print mytemplate.render(name="jack")
如果不希望使用unicode类型,则需要指定特定的input_encoding
mytemplate = Template("你好, ${name}!", input_encoding="utf-8")
print mytemplate.render(name="jack")
二、单模板渲染
mako也可以载入单个模板文件进行渲染,只要指定文件的绝对路径即可
from mako.template import Template
mytemplate = Template(filename='/dir1/a.html')
print mytemplate.render()
如果a.html有特定的编码格式,也需要指定input_encoding
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8")
print mytemplate.render()
Template中还可以指定模板编译之后的中间文件存储路径,可以方便模板语法问题的调试。
mytemplate = Template(filename='/dir1/a.html', input_encoding="utf-8", module_directory='/tmp')
print mytemplate.render()
注意,mako会在/tmp目录下生成/dir1/a.html.py文件,也就是说会将filename中包含的文件路径在module_directory中生成一遍。
但是如果这个模板中包含了include之类的mako语法,这种载入方式就会因找不到文件而报错。
三、模板lookup
在mako最常用的还是TemplateLookup,为mako指定一个模板文件存放路径,而将模板查找工作交给mako处理。
from mako.template import Template
from mako.lookup import TemplateLookup
mylookup = TemplateLookup(directories=['/dir1','/dir2' ])
mytemplate = mylookup.get_template("a.html")
TemplateLookup中,可以指定多个路径,mako按顺序逐个查找,直到找到模板文件。使用了TemplateLookup之后,建议使用get_template函数得到模板。Template类中尽管可以指定lookup参数,但其他参数无法从lookup中取得预先设定的信息,会引起很多错误。
如有一下目录结构:
/dir1
a.html(模板中include了c.html)
c.html(模板有中文内容)
/dir2
b.html
那么
mylookup = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8")
mytemplate = Template(filename="a.html", lookup=mylookup) # 提示找不到a.html文件
mytemplate = Template(filename="/dir1/a.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 提示找不到模板c.html,lookup没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup) # 出错,提示编码出错,也就是说input_encoding没起作用
mytemplate = Template(filename="/dir1/c.html", lookup=mylookup, input_encoding="utf-8") # 正确
print mytemplate.render() # 解析正确
mytemplate = Template(filename="/dir1/b.html", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="a.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = Template("""<%include file="b.html"/>""", lookup=mylookup) # 正确,文件找到
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("a.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template("b.html")
print mytemplate.render() # 解析正确
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render() # 解析正确
TemplateLookup也可以指定模板编译之后的中间文件存储路径。
mytemplate = TemplateLookup(directories=['/dir1','/dir2’ ], input_encoding="utf-8", module_directory='/tmp')
mytemplate = mylookup.get_template(“c.html")
print mytemplate.render()
其文件路径也是module_directory + get_template中指定的相对路径。
四、encoding
除了input_encoding之外,mako需要关注的output_encoding。在不指定输出编码时,render()输出unicode字符串。
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
u'\u4e2d\u6587\n'
输出utf-8
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xe4\xb8\xad\xe6\x96\x87\n'
输出gbk
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="gbk")
>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render()
'\xd6\xd0\xce\xc4\n’
如果使用了render_unicode()函数输出,则output_encoding 参数被忽略
>>> mylookup = TemplateLookup(directories=['/dir1','/dir2' ], input_encoding="utf-8", output_encoding="utf-8")>>> mytemplate = mylookup.get_template("c.html")
>>> mytemplate.render_unicode()
u'\u4e2d\u6587\n'
注:系统环境
OS: OS X 10.9
Python: 2.7.5
Monday, November 18, 2013
使用Pyramid和Cornice构建多格式支持的RESTful API
在Pyramid中利用Cornice可以很方便的基于HTTP Accept header不同构建不同格式的响应内容。
1. 安装Cornice
pip install Cornice
2. 安装Cornice之后,会在系统中增加一个名为cornice的template,在学习时可以使用它创建一个简单的项目框架。
pcreate -t cornice myapp
创建好的项目相比pyramid的项目简单,只有一个__init__.py,一个views.py
3. 在views.py中定义了一个简单的api例子
hello = Service(name='hello', path='/', description="Simplest app")
@hello.get()
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World’}
在该例子中,定义了一个url为/的api,访问这个api链接,将返回一个application/json响应。
4. 在__init__.py中使用include包含了cornice包
from pyramid.config import Configurator
def main(global_config, **settings):
config = Configurator(settings=settings)
config.include("cornice")
config.scan("myapp.views")
return config.make_wsgi_app()
5. 运行该程序,查看结果
python setup.py develop
pserve --reload myapp.ini
我们通过curl访问该url:curl -D - http://0.0.0.0:6543,得到如下响应内容
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Mon, 18 Nov 2013 06:46:49 GMT
Server: waitress
{"Hello": "World"}
从Content-Type中可以看到,取得的内容为application/json格式
6. 构建一个文本格式返回结果
为了取得文本结果,我们需要将view的结果进行格式化,因此在__init__.py中加入如下renderer
class TextRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
request = system.get('request')
if request is not None:
response = request.response
response.content_type = 'text/plain'
name = value['Hello']
return u"Hello, {}!".format(name)
注:如果不需要进行类似上面的数据转化,可以直接使用string renderer。
然后,在main函数中加入config.add_renderer('text', TextRenderer)
7. 修改views.py 以接收不同的HTTP Header
@hello.get(accept='text/plain', renderer='text')
@hello.get(accept='application/json', renderer='json')
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World'}
上面的两个@分别表示当get中的HTTP Header不同时的处理(使用不同的renderer对数据进行不同渲染)
8. 使用不同的HTTP Header查看结果
$ curl -D - -H 'Accept: text/plain' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:41 GMT
Server: waitress
Hello, World!
$ curl -D - -H 'Accept: application/json' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:48 GMT
Server: waitress
{"Hello": "World"}
9. cornice与参数校验的整合
cornice可以很好的跟colander校验库结合使用。下面我们加入一个输入参数尝试一下。
9.1 安装colander
pip install colander
9.2 在views.py中增加一个校验规则
import colander
class NameSchema(colander.MappingSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(2))
该校验规则表示系统接收一个名为name的参数,且该参数为字符串类型,最小长度为2(Length参数为min,max,这里省略了max)
9.3 在views.py中增加参数处理方法
@hello.post(accept='text/plain', renderer='text', schema=NameSchema)
@hello.post(accept='application/json', renderer='json', schema=NameSchema)
def post_info(request):
"""Returns Hello in JSON."""
name = request.validated['name']
return {'Hello': name}
上面的方法为/这个url增加了post处理。
9.4 取得运行结果
curl -H 'Accept: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
结果:{"status": "error", "errors": [{"location": "body", "name": "name", "description": "name is missing"}]}
这是因为cornice默认接收的Content-Type是application/x-www-form-urlencoded,因此不能解析-d '{"name": "Alex"}' 这种参数
$ curl -H 'Accept: application/json' http://0.0.0.0:6543 -d 'name=Alex'
{"Hello": "Alex"}
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
{"Hello": "Alex"}
该例子中指定了Content-Type,因此系统能识别-d '{"name": "Alex"}'
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "A"}'
{"status": "error", "errors": [{"location": "body", "name": "name", "description": "Shorter than minimum length 2"}]}
本文是http://makina-corpus.com/blog/metier/multi-format-restful-api-with-cornice的一个摘要翻译,需要查看原文请自行前往。
1. 安装Cornice
pip install Cornice
2. 安装Cornice之后,会在系统中增加一个名为cornice的template,在学习时可以使用它创建一个简单的项目框架。
pcreate -t cornice myapp
创建好的项目相比pyramid的项目简单,只有一个__init__.py,一个views.py
3. 在views.py中定义了一个简单的api例子
hello = Service(name='hello', path='/', description="Simplest app")
@hello.get()
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World’}
在该例子中,定义了一个url为/的api,访问这个api链接,将返回一个application/json响应。
4. 在__init__.py中使用include包含了cornice包
from pyramid.config import Configurator
def main(global_config, **settings):
config = Configurator(settings=settings)
config.include("cornice")
config.scan("myapp.views")
return config.make_wsgi_app()
5. 运行该程序,查看结果
python setup.py develop
pserve --reload myapp.ini
我们通过curl访问该url:curl -D - http://0.0.0.0:6543,得到如下响应内容
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Mon, 18 Nov 2013 06:46:49 GMT
Server: waitress
{"Hello": "World"}
从Content-Type中可以看到,取得的内容为application/json格式
6. 构建一个文本格式返回结果
为了取得文本结果,我们需要将view的结果进行格式化,因此在__init__.py中加入如下renderer
class TextRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
request = system.get('request')
if request is not None:
response = request.response
response.content_type = 'text/plain'
name = value['Hello']
return u"Hello, {}!".format(name)
注:如果不需要进行类似上面的数据转化,可以直接使用string renderer。
然后,在main函数中加入config.add_renderer('text', TextRenderer)
7. 修改views.py 以接收不同的HTTP Header
@hello.get(accept='text/plain', renderer='text')
@hello.get(accept='application/json', renderer='json')
def get_info(request):
"""Returns Hello in JSON."""
return {'Hello': 'World'}
上面的两个@分别表示当get中的HTTP Header不同时的处理(使用不同的renderer对数据进行不同渲染)
8. 使用不同的HTTP Header查看结果
$ curl -D - -H 'Accept: text/plain' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:41 GMT
Server: waitress
Hello, World!
$ curl -D - -H 'Accept: application/json' http://0.0.0.0:6543
HTTP/1.1 200 OK
Content-Length: 18
Content-Type: application/json; charset=UTF-8
Date: Fri, 15 Nov 2013 11:22:48 GMT
Server: waitress
{"Hello": "World"}
9. cornice与参数校验的整合
cornice可以很好的跟colander校验库结合使用。下面我们加入一个输入参数尝试一下。
9.1 安装colander
pip install colander
9.2 在views.py中增加一个校验规则
import colander
class NameSchema(colander.MappingSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(2))
该校验规则表示系统接收一个名为name的参数,且该参数为字符串类型,最小长度为2(Length参数为min,max,这里省略了max)
9.3 在views.py中增加参数处理方法
@hello.post(accept='text/plain', renderer='text', schema=NameSchema)
@hello.post(accept='application/json', renderer='json', schema=NameSchema)
def post_info(request):
"""Returns Hello in JSON."""
name = request.validated['name']
return {'Hello': name}
上面的方法为/这个url增加了post处理。
9.4 取得运行结果
curl -H 'Accept: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
结果:{"status": "error", "errors": [{"location": "body", "name": "name", "description": "name is missing"}]}
这是因为cornice默认接收的Content-Type是application/x-www-form-urlencoded,因此不能解析-d '{"name": "Alex"}' 这种参数
$ curl -H 'Accept: application/json' http://0.0.0.0:6543 -d 'name=Alex'
{"Hello": "Alex"}
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "Alex"}'
{"Hello": "Alex"}
该例子中指定了Content-Type,因此系统能识别-d '{"name": "Alex"}'
$ curl -H 'Accept: application/json' -H 'Content-Type: application/json' http://0.0.0.0:6543 -d '{"name": "A"}'
{"status": "error", "errors": [{"location": "body", "name": "name", "description": "Shorter than minimum length 2"}]}
本文是http://makina-corpus.com/blog/metier/multi-format-restful-api-with-cornice的一个摘要翻译,需要查看原文请自行前往。
Subscribe to:
Posts (Atom)