Saturday, November 30, 2013

mako二三事 -- 函数一

跟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取变量这个步骤了,也因此,这段代码显然就报错了。






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

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中参数的不同组合,可以在没有业务逻辑程序的支持下直接调试模板,方便了模板的编写和分工。

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为出错信息

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

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的一个摘要翻译,需要查看原文请自行前往。