五、匹配规则案例
下面通过几种可能的案例及其匹配之后的matchdict内容来解释一下这个匹配规则。
1. 规则 foo/{baz}/{bar}
foo/1/2 -> {’baz’:u’1’, ’bar’:u’2’} 最普通的匹配方式foo/abc/def -> {’baz’:u’abc’, ’bar’:u’def’} 注意,这里的值是unicode的
foo/1/2/ -> 不匹配,因为最后有一个'/'
bar/abc/def -> 第一段就无法匹配
2. 规则 foo/{name}.{ext}
foo/biz.html -> {’name’: u’biz’, ’ext’: u’html’}
/foo/biz -> 不匹配,没有'.'
3. 规则 /Foo Bar/{baz}
/Foo%20Bar/abc -> {'baz': u'abc'}
4. 规则 foo/{baz}/{bar}*fizzle
foo/1/2/ -> {’baz’:u’1’, ’bar’:u’2’, ’fizzle’:()}
foo/abc/def/a/b/c -> {’baz’:u’abc’, ’bar’:u’def’, ’fizzle’:(u’a’, u’b’, u’c’)}
5. 规则 foo/*fizzle
/foo/La%20Pe%C3%B1a/a/b/c -> {’fizzle’:(u’La Pe\xf1a’, u’a’, u’b’, u’c’)}
6. 规则 foo/{baz}/{bar}{fizzle:.*}
foo/1/2/ -> {’baz’:u’1’, ’bar’:u’2’, ’fizzle’:()}
foo/abc/def/a/b/c -> {’baz’:u’abc’, ’bar’:u’def’, ’fizzle’: u’a/b/c’)}
这里的.*是正则表达式,表示通配所有字符,跟例子4是不一样的,而且这里需要{}将占位符包含起来。
六、生成URL
在程序开发中,经常需要跳转到一个route对应的URL,这个时候就需要有一种方式来将route的定义转成一个实际的url了。Pyramid提供了url、path两种声称方式,url是指包含了当前protocol与hostname在内完整url。如果要生成不包含protocol与hostname的url则需要使用path方式。
如,定义了一个规则'{a}/{b}/{c}',那么
request.route_url(’foo’, a=’1’, b=’2’, c=’3’)可以得到http://example.com/1/2/3
request.route_path(’foo’, a=’1’, b=’2’, c=’3’)可以得到/1/2/3
在生成url的时候也要注意unicode,例如加了一条规则config.add_route(’la’, u’/La Peña/{city}’)
那么request.route_path(’la’, city=u’Québec’) 可以得到/La%20Pe%C3%B1a/Qu%C3%A9bec
使用了*通配符的规则,例如加了一条规则config.add_route(’abc’, ’a/b/c/*foo’)
request.route_path(’abc’, foo=u’Québec/biz’) 可以得到/a/b/c/Qu%C3%A9bec/biz
request.route_path(’abc’, foo=(u’Québec’, u’biz’)) 也可以得到 /a/b/c/Qu%C3%A9bec/biz
与route_url类似的还有static_url,经常需要在模版中使用它。
七、定制断言
有的时候,为了更精确的定位route,除了系统自带的断言之外,我们还会使用一些自定义的断言。这个时候就需要使用custom_predicates这个参数了。custom_predicates参数是一个包含了一个或多个断言的tuple。每一个断言接受两个参数,第一个参数是一个字典,一般叫info,其中有包含了一个叫match的字典,其内容为URL分解后的占位符名值对;还包含一个名为route的对象,指向当前匹配的Route。断言的第二个参数就是当前处理的request。
例如,我们需要某一个url片段是在'one','two','three'三者选其一。我们可以写如下的程序段:
从这个程序片段,我们可以看出:
1. 因需要动态传入'one','two','three'这些参数,所以定义了一个any_of函数。
2. any_of返回的是一个predicte可执行对象,该对象接受info,request两个参数。就是我们需要的断言。
3. 该断言查找到match中有否num这个字段,并且其值在'one','two','three'之中,则断言为真
4. custom_predicates接受一个tuple,可以一次传入多个断言,断言之间是 并且 的关系。任何一个断言不匹配,则不匹配该route。
另外,定制的断言还可以变更match这个字典中的值,从而可以完成一些更有趣的事情。例如
该程序判断match中的名值对,如果其名字是year、month、day中之一,就将其值转为整型。注意,这里必须返回True,以确保断言的要求。
进一步,我们可以将上面的程序做如下改写:
这段程序中,直接在匹配规则中定义了正则表达式以确保符合的URL中的year、month、day一定是数字,因此在断言里面就不需要用try/catch了。不过因为这里变更了match字典中的值,因此要严格注意断言的顺序,以免几个断言之间产生冲突,建议变更内容的断言放在断言tuple的最后。
我们再来看看info中另外一个属性: route,它指向了当前操作中的route,因此,它有name,pattern等route特有的属性,因此我们可以得到下面的程序
该程序就是判定如果route名是'y'、'ym'、'ymd'三者之一,就断言其year值必须是2010。
Sunday, March 25, 2012
使用URL分发方式配置Pyramid项目(上)
本节资料整理改编自Pyramid官方网站,部分内容按理解进行了增删整理和归纳,以便结构上更清晰。
Pyramid中可以使用URL分发作为其URL映射机制,这种方式来源于其前身Pylons中引用的Routes项目,通过定义一系列有序的URL匹配规则来将每一个request请求关联到合适的view上。如果找不到任何一条相匹配的规则,Pyramid将转而采用漫游(traversal)方式来定位view。在URL规划过程时,可以在ini文件中设置pyramid.debug_routematch为true来打开调试模式以便观察规则匹配结果。
一、Route配置
通常地,在开发过程中,route跟view是相匹配出现地。Pyramid允许使用如下两种定义方式来完成配置:
1. 同时add_route、add_view。
config.add_route(’myroute’, ’/prefix/{one}/{two}’)
config.add_view(myview, route_name=’myroute’)
在这里,add_route的第一个参数是这个route的名字,第二个参数是用于匹配的规则。add_view的第一个参数是view对象,也可以是一个类似'mypackage.views.myview'这样的带包结构的view名。第二个参数是一个断言,表明只有route_name为‘myroute’时才能调用这个view。这两个函数均可以带一系列断言参数来缩小匹配范围。
2. 使用scan扫描view定义
在main函数中定义route,然后使用scan方法扫描整个包,载入标注的view。
config.add_route(’myroute’, ’/prefix/{one}/{two}’)
config.scan(’mypackage’)
在view中,则用view_config来标注对应的view
@view_config(route_name=’myroute’)
def myview(request):
return Response(’OK’)
这种方式其配置比较靠近实现代码,因此从阅读上更清晰,比较受到开发人员的欢迎。
二、规则语法
这里的匹配规则主要是指上面例子中add_route的第二个参数,即’/prefix/{one}/{two}’。在Pyramid中,定义了一个比较直观的匹配算法灵活地实现了各种URL的匹配。
规则1. 前导‘/’可有可无,‘{foo}/bar/baz’跟‘/{foo}/bar/baz’两则是等价的。
规则2. 每一个匹配段(在'/'之间的字符串)均可以是一个既定字符串,也可以是一个占位符,甚至可以是两者的组合。如‘foo/{name}.html’
规则3. 匹配段必须至少包含一个字符。如‘/abc/’这个url跟‘/abc/{foo}’无法匹配,因foo不能是空地,但跟‘/{foo}/’可以匹配。
规则4. 占位符可以附带一个正则表达式以进一步缩小匹配范围。如{foo:\d+}表示只匹配数字内容。其实单独的占位符都隐含了一个通配地正则表达式,即{foo}就是{foo:[^/]+},匹配不包含'/'的一个或多个任意字符。
规则5. 可以使用*号加占位符来通配URL剩余部分。*只能放在匹配规则的最后,而且无需‘/’前导。如foo/{baz}/{bar}*fizzle
规则6. 规则严格按照申明顺序进行匹配,一旦发现匹配即中止匹配过程,返回匹配的route。
三、匹配算法
编写route配置的最终目的是为了将匹配规则与WSGI中的PATH_INFO相匹配(或不匹配)。这个处理过程很简单,当一个请求来临的时候,Pyramid会按照定义的顺序逐条检查所有匹配规则。这里要注意的是,除了上面我们看到的名字,匹配规则这两个参数之外,add_route还可以带一些所谓的断言参数,必须所有的断言参数都为真才能判定一条route配置匹配,否则即为不匹配,跳过这条规则检查下一条,直到检查完所有定义的规则。
一旦找到匹配的规则,Pyramid即启动view寻找机制定位该匹配route最合理地view。如果遍历完所有规则还是找不到匹配,Pyramid则启动漫游机制进行resouce定位和view定位。
作为匹配过程地一个自然结果,匹配算法将会在request中添加两个跟其相关的属性:matchdict、matched_route。matchdict是一个包含了占位符名与其匹配值对的字典。这里要注意一下的是,占位符名的类型是string,对应值的类型是Unicode,URL中对应部分将会通过url decode进行转换,并将utf-8转成Unicode之后才进入matchdict。
四、函数参数
add_route除了name和pattern之外,还可以带一系列的参数,这些参数大致可以分为普通和断言两种类型,其中断言参数参与匹配算法,如果断言参数不匹配,该规则即不匹配。
1. 普通参数
name: route名,必填,单一应用唯一。
factory: 当该route匹配时用于生成资源根对象(root resource object)的可执行对象(函数或类),不指定系统将采用默认的root factory。
traverse: 指定特定的漫游路径
pregenerator: 通过route产生url时的前置操作,用于变更route_url传入的参数,不常用。
use_global_views: 当view定位时找不到一个route_name相同的view时,是否需要看看context,request,view名匹配而route_name不匹配的view
static: 是否是静态route,静态意味着该route只能用于生成url,不能用于request匹配,也就是不用通过url访问到它。
2. 断言参数
pattern: 匹配规则
xhr: 匹配时是否处理HTTP_X_REQUESTED_WITH,处理AJAX请求时常用。
request_method: ‘GET’、‘POST’、‘HEAD’、‘DELETE’、‘PUT’之一或是一个它们的tuple组合。不指定通配所有请求。
path_info: 匹配PATH_INFO的正则表达式。
request_param: 指定GET、POST必含的参数,如使用了request_param="foo=123"这种方式,则必须参数名、参数值都对应才算匹配。
header: 指定请求中必含的的HTTP header或header名值对。如‘User-Agent:Mozilla/.*’、'Host:localhost'
accept: 指定HTTP 请求头中客户端可以能够接受的内容类型,如'text/plain'、'text/*'、'*/*'
custom_predicates: 定制的断言可执行对象。
Pyramid中可以使用URL分发作为其URL映射机制,这种方式来源于其前身Pylons中引用的Routes项目,通过定义一系列有序的URL匹配规则来将每一个request请求关联到合适的view上。如果找不到任何一条相匹配的规则,Pyramid将转而采用漫游(traversal)方式来定位view。在URL规划过程时,可以在ini文件中设置pyramid.debug_routematch为true来打开调试模式以便观察规则匹配结果。
一、Route配置
通常地,在开发过程中,route跟view是相匹配出现地。Pyramid允许使用如下两种定义方式来完成配置:
1. 同时add_route、add_view。
config.add_route(’myroute’, ’/prefix/{one}/{two}’)
config.add_view(myview, route_name=’myroute’)
在这里,add_route的第一个参数是这个route的名字,第二个参数是用于匹配的规则。add_view的第一个参数是view对象,也可以是一个类似'mypackage.views.myview'这样的带包结构的view名。第二个参数是一个断言,表明只有route_name为‘myroute’时才能调用这个view。这两个函数均可以带一系列断言参数来缩小匹配范围。
2. 使用scan扫描view定义
在main函数中定义route,然后使用scan方法扫描整个包,载入标注的view。
config.add_route(’myroute’, ’/prefix/{one}/{two}’)
config.scan(’mypackage’)
在view中,则用view_config来标注对应的view
@view_config(route_name=’myroute’)
def myview(request):
return Response(’OK’)
这种方式其配置比较靠近实现代码,因此从阅读上更清晰,比较受到开发人员的欢迎。
二、规则语法
这里的匹配规则主要是指上面例子中add_route的第二个参数,即’/prefix/{one}/{two}’。在Pyramid中,定义了一个比较直观的匹配算法灵活地实现了各种URL的匹配。
规则1. 前导‘/’可有可无,‘{foo}/bar/baz’跟‘/{foo}/bar/baz’两则是等价的。
规则2. 每一个匹配段(在'/'之间的字符串)均可以是一个既定字符串,也可以是一个占位符,甚至可以是两者的组合。如‘foo/{name}.html’
规则3. 匹配段必须至少包含一个字符。如‘/abc/’这个url跟‘/abc/{foo}’无法匹配,因foo不能是空地,但跟‘/{foo}/’可以匹配。
规则4. 占位符可以附带一个正则表达式以进一步缩小匹配范围。如{foo:\d+}表示只匹配数字内容。其实单独的占位符都隐含了一个通配地正则表达式,即{foo}就是{foo:[^/]+},匹配不包含'/'的一个或多个任意字符。
规则5. 可以使用*号加占位符来通配URL剩余部分。*只能放在匹配规则的最后,而且无需‘/’前导。如foo/{baz}/{bar}*fizzle
规则6. 规则严格按照申明顺序进行匹配,一旦发现匹配即中止匹配过程,返回匹配的route。
三、匹配算法
编写route配置的最终目的是为了将匹配规则与WSGI中的PATH_INFO相匹配(或不匹配)。这个处理过程很简单,当一个请求来临的时候,Pyramid会按照定义的顺序逐条检查所有匹配规则。这里要注意的是,除了上面我们看到的名字,匹配规则这两个参数之外,add_route还可以带一些所谓的断言参数,必须所有的断言参数都为真才能判定一条route配置匹配,否则即为不匹配,跳过这条规则检查下一条,直到检查完所有定义的规则。
一旦找到匹配的规则,Pyramid即启动view寻找机制定位该匹配route最合理地view。如果遍历完所有规则还是找不到匹配,Pyramid则启动漫游机制进行resouce定位和view定位。
作为匹配过程地一个自然结果,匹配算法将会在request中添加两个跟其相关的属性:matchdict、matched_route。matchdict是一个包含了占位符名与其匹配值对的字典。这里要注意一下的是,占位符名的类型是string,对应值的类型是Unicode,URL中对应部分将会通过url decode进行转换,并将utf-8转成Unicode之后才进入matchdict。
四、函数参数
add_route除了name和pattern之外,还可以带一系列的参数,这些参数大致可以分为普通和断言两种类型,其中断言参数参与匹配算法,如果断言参数不匹配,该规则即不匹配。
1. 普通参数
name: route名,必填,单一应用唯一。
factory: 当该route匹配时用于生成资源根对象(root resource object)的可执行对象(函数或类),不指定系统将采用默认的root factory。
traverse: 指定特定的漫游路径
pregenerator: 通过route产生url时的前置操作,用于变更route_url传入的参数,不常用。
use_global_views: 当view定位时找不到一个route_name相同的view时,是否需要看看context,request,view名匹配而route_name不匹配的view
static: 是否是静态route,静态意味着该route只能用于生成url,不能用于request匹配,也就是不用通过url访问到它。
2. 断言参数
pattern: 匹配规则
xhr: 匹配时是否处理HTTP_X_REQUESTED_WITH,处理AJAX请求时常用。
request_method: ‘GET’、‘POST’、‘HEAD’、‘DELETE’、‘PUT’之一或是一个它们的tuple组合。不指定通配所有请求。
path_info: 匹配PATH_INFO的正则表达式。
request_param: 指定GET、POST必含的参数,如使用了request_param="foo=123"这种方式,则必须参数名、参数值都对应才算匹配。
header: 指定请求中必含的的HTTP header或header名值对。如‘User-Agent:Mozilla/.*’、'Host:localhost'
accept: 指定HTTP 请求头中客户端可以能够接受的内容类型,如'text/plain'、'text/*'、'*/*'
custom_predicates: 定制的断言可执行对象。
Friday, March 23, 2012
Pyramid是怎么处理每个Request请求的?
本节内容基本译自Pyramid官方网站,添加了一些自己的标注。
本节基本就是router.py这个模块的一一对应,需要加深理解最好打开源代码一边看代码一边看说明(本文针对Pyramid 1.3b2代码)。
上一篇说了Pyramid项目里main函数使用make_wsgi_app生成了一个名字叫Router的app,并将这个app传给了WSGI服务器。那么,显而易见,Pyramid处理Request请求就是跟这个Router类息息相关了。
1. WSGI服务器一旦接受到用户请求,即按照规范要求构造WSGI环境变量,然后将这些变量传递给router对象(app)的__call__方法。(参见paste deploy)
2. router使用request_factory创建一个request对象,并将WSGI环境变量传递给request对象。从这种处理方式可以看出,我们甚至可以自定义一个定制化的request_factory,以便生成特有的request。(参加router.py 179行)
3. 将request、registry对象放入到thread local栈中,以使每次请求之间的数据不会有冲突。今后可以用get_current_request()、get_current_registry()来得到这两个对象,不过在view中建议采用直接采用request、request.registry。(参加router.py 180-183行)
4. 触发一个NewRequest事件。(参加router.py 75行)
5. 如果在之前的应用配置处(main函数)配置了route配置项,Pyramid会调用routes_mapper函数进行URL分发。该函数检查预定义的route中(main函数)是否有与request包含的当前WSGI变量相匹配的项。(参加router.py 79行)
6. 如果找到匹配项,route_mapper函数会在request中加入两个属性:matchdict、matched_route。matchdict是一个针对具体PATH_INFO与预定义route项匹配之后形成的动态参数。比如定义了add_route(’idea’, ’site/{id}’),那么/site/1请求就会形成一个值为{’id’:’1’}的matchdict。matched_route则是指对应的那条route对象(参加router.py 94-95行)。随后,生成该route对应的root对象(参加router.py 120行)。如果这条route中配置了factory,则采用该factory生成root对象,否则采用默认的root_factory。(参见48、77、118行)
7. 如果没有找到相匹配的route,而且在创建Configurator对象时指定了root_factory参数,则使用该root_factory创建root对象。如果没有指定root_factory参数,则使用DefaultRootFactory来创建root对象。(参见48、77、118行)
8. Pyramid通过root、request参数漫游(traverser),traverser从root对象开始漫游(__getitem__方法)以寻找合适的context,如果root对象没有__getitem__方法,则将root对象赋给context。traverser返回一个带context、view名字等信息的字典。(参加router.py124-136行)
9. 将上一步取得的参数添加到request对象中,因此在view代码中,可以用request.context这种方式来访问他们。(参见router.py 138行)
10. 触发contextFound事件(参见router.py 139行)
11. 使用context、request、view名字等信息查找view,如果找不到,则抛出HTTPNotFound 异常(参见router.py 141-162行)
12. 如果找到了合适的view,Pyramid查看是否已经定义了认证策略,且这个view配置了访问权限。如是,则Pyramid将request中的访问者凭证与context附带的安全信息进行匹配,如果匹配通过,Pyramid则调用该view并且获得response对象。否则抛出HTTPForbidden异常。
13. 如果在上述过程中(root factory、traversal、view)抛出了异常,如HTTPNotFound、 HTTPForbidden,router将捕获这些异常,并赋给request.exception属性。然后寻找一个合适该异常的view,如果有这样的view,就调用它,产生response对象。如果找不到,就抛出该异常。
14. 触发NewResponse事件(参见router.py 188行)
15. 当一个response对象通过view或exception view生成之后,Pyramid将会遍历执行所有通过add_response_callback方法加进来的方法、对象。(参加 router.py 190-192 行)
16. 遍历执行通过add_finished_callback加入进来的方法、对象。(参见router.py 196-197 行)
17. 将threadlocal从栈中弹出。(参见 router.py 200 行)
本节基本就是router.py这个模块的一一对应,需要加深理解最好打开源代码一边看代码一边看说明(本文针对Pyramid 1.3b2代码)。
上一篇说了Pyramid项目里main函数使用make_wsgi_app生成了一个名字叫Router的app,并将这个app传给了WSGI服务器。那么,显而易见,Pyramid处理Request请求就是跟这个Router类息息相关了。
1. WSGI服务器一旦接受到用户请求,即按照规范要求构造WSGI环境变量,然后将这些变量传递给router对象(app)的__call__方法。(参见paste deploy)
2. router使用request_factory创建一个request对象,并将WSGI环境变量传递给request对象。从这种处理方式可以看出,我们甚至可以自定义一个定制化的request_factory,以便生成特有的request。(参加router.py 179行)
3. 将request、registry对象放入到thread local栈中,以使每次请求之间的数据不会有冲突。今后可以用get_current_request()、get_current_registry()来得到这两个对象,不过在view中建议采用直接采用request、request.registry。(参加router.py 180-183行)
4. 触发一个NewRequest事件。(参加router.py 75行)
5. 如果在之前的应用配置处(main函数)配置了route配置项,Pyramid会调用routes_mapper函数进行URL分发。该函数检查预定义的route中(main函数)是否有与request包含的当前WSGI变量相匹配的项。(参加router.py 79行)
6. 如果找到匹配项,route_mapper函数会在request中加入两个属性:matchdict、matched_route。matchdict是一个针对具体PATH_INFO与预定义route项匹配之后形成的动态参数。比如定义了add_route(’idea’, ’site/{id}’),那么/site/1请求就会形成一个值为{’id’:’1’}的matchdict。matched_route则是指对应的那条route对象(参加router.py 94-95行)。随后,生成该route对应的root对象(参加router.py 120行)。如果这条route中配置了factory,则采用该factory生成root对象,否则采用默认的root_factory。(参见48、77、118行)
7. 如果没有找到相匹配的route,而且在创建Configurator对象时指定了root_factory参数,则使用该root_factory创建root对象。如果没有指定root_factory参数,则使用DefaultRootFactory来创建root对象。(参见48、77、118行)
8. Pyramid通过root、request参数漫游(traverser),traverser从root对象开始漫游(__getitem__方法)以寻找合适的context,如果root对象没有__getitem__方法,则将root对象赋给context。traverser返回一个带context、view名字等信息的字典。(参加router.py124-136行)
9. 将上一步取得的参数添加到request对象中,因此在view代码中,可以用request.context这种方式来访问他们。(参见router.py 138行)
10. 触发contextFound事件(参见router.py 139行)
11. 使用context、request、view名字等信息查找view,如果找不到,则抛出HTTPNotFound 异常(参见router.py 141-162行)
12. 如果找到了合适的view,Pyramid查看是否已经定义了认证策略,且这个view配置了访问权限。如是,则Pyramid将request中的访问者凭证与context附带的安全信息进行匹配,如果匹配通过,Pyramid则调用该view并且获得response对象。否则抛出HTTPForbidden异常。
13. 如果在上述过程中(root factory、traversal、view)抛出了异常,如HTTPNotFound、 HTTPForbidden,router将捕获这些异常,并赋给request.exception属性。然后寻找一个合适该异常的view,如果有这样的view,就调用它,产生response对象。如果找不到,就抛出该异常。
14. 触发NewResponse事件(参见router.py 188行)
15. 当一个response对象通过view或exception view生成之后,Pyramid将会遍历执行所有通过add_response_callback方法加进来的方法、对象。(参加 router.py 190-192 行)
16. 遍历执行通过add_finished_callback加入进来的方法、对象。(参见router.py 196-197 行)
17. 将threadlocal从栈中弹出。(参见 router.py 200 行)
Thursday, March 22, 2012
Pyramid启动时都能干些啥?
Pyramid找到我们项目的main函数之后,一切就相对简单了。
一、项目参数的传递
我们打开项目中的__init__.py文件,找到自动生成的main函数如下:
我们可以看到,main函数带了两个参数,global_config和setting。这是由Paste deploy传递过来的解析自development.ini的参数项。
其中global_config是development.ini中[DEFAULT]这一节中的参数定义,在我们自动生成的例子中,我们没有使用default节,所以也就没有传入自定义的参数,系统默认在global_config这个字典对象中加了两个参数项定义:
here: 表示development.ini文件所在目录的绝对路径
__file__: 表示development.ini文件的绝对路径
setting字典则包含了所有定义在[app:main]中的参数项(除了use),在我们的例子中,就包含了pyramid.reload_templates、pyramid.debug_authorization、sqlalchemy.url等的参数项。这些参数项都可以直接在我们的程序中使用。我们也可以在[app:main]中定义自定义的参数项,如mytest=testconfig。那么我们在view中就可以用如下程序取得定义的值。
settings = request.registry.settings
mytest = settings[’mytest’]
这里注意一下,放在其他section中的参数是传不进来的,如果一定要这样用,需要在程序中取到ini文件路径(global_config中的__file__),然后自行使用ConfigParser读取解析。
二、Configurator的配置
就如上面的程序,main函数做的最主要的工作就是做整合项目的配置,形成注册表。在Pyramid中可以配置的东西很多,我们可以简单看下几个常用的配置:
1. 静态资源
通过config.add_static_view('static', 'static', cache_max_age=3600)加入一个静态资源目录,一般写成config.add_static_view(name=’static’, path=’/var/www/static’) 或 config.add_static_view(name=’static’, path=’mypackage:a/b/c/static’) 这样的方式更清晰。
在add_static_view中,name代表了静态文件URL的前缀,如果我们使用了上面前一个例子,那么URL /static/foo.css 就会取指向文件/var/www/static/foo.css。后一个例子是相对与项目包结构的相对路径。
不过现在很多程序都是用外部资源来做静态资源的,也可以用config.add_static_view(name=’http://example.com/images’, path=’mypackage:static’)这种方式将静态文件指向外部服务器。
在程序中,就可以用static_url('pyramidkoans:static/favicon.ico') 或 static_path('pyramidkoans:static/favicon.ico') 这样的方式来引用静态资源。按照之前不同的定义,这里会解析出不同的结果。
2. add_route、add_view、view_config、scan
这几个函数定义了从URL到view的映射关系,add_route只在Route模式才会使用,traversal模式下不用。
view的配置可以使用add_view手工加入,也可以使用view_config标注+scan函数合作完成。
3. 权限
pyramid整合了一套ACL权限管理体系,也需要在configurtor中进行配置才能生效。pyramid权限管理划分了认证、授权两个部分,需要分别配置。
authn_policy = AuthTktAuthenticationPolicy('sosecret', callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
4. 用include包含子模块
项目大了之后,可以划分成多个子模块,然后用include包含子模块。
5.subscriber
Pyramid定义了一套简单的事件系统,开发者可以通过订阅一些特定的事件来改变框架的一些默认行为。
三、app的生成
完成了configurator的配置之后,pyramid就调用make_wsgi_app来生成一个符合WSGI规范的app,以便提供服务。
不过这个app是一个叫Router的类的实例,很奇怪吧。
好了,接下来就等待访问链接的来临吧
一、项目参数的传递
我们打开项目中的__init__.py文件,找到自动生成的main函数如下:
我们可以看到,main函数带了两个参数,global_config和setting。这是由Paste deploy传递过来的解析自development.ini的参数项。
其中global_config是development.ini中[DEFAULT]这一节中的参数定义,在我们自动生成的例子中,我们没有使用default节,所以也就没有传入自定义的参数,系统默认在global_config这个字典对象中加了两个参数项定义:
here: 表示development.ini文件所在目录的绝对路径
__file__: 表示development.ini文件的绝对路径
setting字典则包含了所有定义在[app:main]中的参数项(除了use),在我们的例子中,就包含了pyramid.reload_templates、pyramid.debug_authorization、sqlalchemy.url等的参数项。这些参数项都可以直接在我们的程序中使用。我们也可以在[app:main]中定义自定义的参数项,如mytest=testconfig。那么我们在view中就可以用如下程序取得定义的值。
settings = request.registry.settings
mytest = settings[’mytest’]
这里注意一下,放在其他section中的参数是传不进来的,如果一定要这样用,需要在程序中取到ini文件路径(global_config中的__file__),然后自行使用ConfigParser读取解析。
二、Configurator的配置
就如上面的程序,main函数做的最主要的工作就是做整合项目的配置,形成注册表。在Pyramid中可以配置的东西很多,我们可以简单看下几个常用的配置:
1. 静态资源
通过config.add_static_view('static', 'static', cache_max_age=3600)加入一个静态资源目录,一般写成config.add_static_view(name=’static’, path=’/var/www/static’) 或 config.add_static_view(name=’static’, path=’mypackage:a/b/c/static’) 这样的方式更清晰。
在add_static_view中,name代表了静态文件URL的前缀,如果我们使用了上面前一个例子,那么URL /static/foo.css 就会取指向文件/var/www/static/foo.css。后一个例子是相对与项目包结构的相对路径。
不过现在很多程序都是用外部资源来做静态资源的,也可以用config.add_static_view(name=’http://example.com/images’, path=’mypackage:static’)这种方式将静态文件指向外部服务器。
在程序中,就可以用static_url('pyramidkoans:static/favicon.ico') 或 static_path('pyramidkoans:static/favicon.ico') 这样的方式来引用静态资源。按照之前不同的定义,这里会解析出不同的结果。
2. add_route、add_view、view_config、scan
这几个函数定义了从URL到view的映射关系,add_route只在Route模式才会使用,traversal模式下不用。
view的配置可以使用add_view手工加入,也可以使用view_config标注+scan函数合作完成。
3. 权限
pyramid整合了一套ACL权限管理体系,也需要在configurtor中进行配置才能生效。pyramid权限管理划分了认证、授权两个部分,需要分别配置。
authn_policy = AuthTktAuthenticationPolicy('sosecret', callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
4. 用include包含子模块
项目大了之后,可以划分成多个子模块,然后用include包含子模块。
5.subscriber
Pyramid定义了一套简单的事件系统,开发者可以通过订阅一些特定的事件来改变框架的一些默认行为。
三、app的生成
完成了configurator的配置之后,pyramid就调用make_wsgi_app来生成一个符合WSGI规范的app,以便提供服务。
不过这个app是一个叫Router的类的实例,很奇怪吧。
好了,接下来就等待访问链接的来临吧
Wednesday, March 21, 2012
利用Amazon SES为应用构建简单的邮件服务
Amazon SES是AWS推出的一项邮件发送服务,类似于sendgrid等专业的邮件发送服务商。不过因AWS众多其他服务的捆绑支持,对于AWS平台上的应用还是具有不小的吸引力的。
一. 为啥使用SES呢
1. 对于发送量不稳定的应用,价格还是便宜的,发多少,算多少($0.10/1000条),没有套餐限制,附带流量也会产生费用。
2. 对于AWS EC2上的应用,有每天2000封的免费额度,基本足够大部分人使用了。
当然,也要牢记,amazon不是专业的mail发送公司,涉及到更专业的送达服务、垃圾邮件等方面还不那么健全,普通用用是完全没问题的,毕竟大家都知道邮件是怎么回事,有点小问题也都能谅解,顶多勤快点,多泡泡amazon服务论坛。
二. SES接入模式
amazon提供了多种接入支持以满足各种不同的需要:
1. smtp发送服务,提供了STARTTLS、TLS Wrapper两种不同方式,端口不同,可以用于编程或安装应用软件配置
2. api发送服务,可以通过amazon的api发送邮件,这里提供了一个封装。https://github.com/pankratiev/python-amazon-ses-api/blob/master/amazon_ses.py3. 与 Postfix 整合,配置其relayhost参数,使已有的应用能方便的使用SES服务能力。
三. smtp接入案例
1. 发件人认证
SES需要对每一个发件人邮箱进行认证,不存在的发件人不能够发送邮件。只需要再verify a new sender里面输入邮件地址,SES会往这个邮箱发送一个验证URL,在浏览器打开该URL即可完成验证。
SES 工作在sanbox状态下,需要同时将收信人也进行同样的认证。如果需要在生产环境使用,需要事先申请,24小时开通。
2. 生成SMTP 用户密码对
在发信之前,还需要生成一个smtp用户密码对,只需要点击SMTP Settings 中的Create My SMTP Credentials按钮即可在IAM中生成一个账号,同时显示其名字、密码,记录下来保存好需要在发送邮件时使用。
3. 小程序
mailServer = smtplib.SMTP_SSL('email-smtp.us-east-1.amazonaws.com', 465)
mailServer.set_debuglevel(1)
mailServer.login(IAM用户名, IAM用户密码)
mailServer.sendmail('发件人', ['收件人'], msg.as_string())
mailServer.close()
一. 为啥使用SES呢
1. 对于发送量不稳定的应用,价格还是便宜的,发多少,算多少($0.10/1000条),没有套餐限制,附带流量也会产生费用。
2. 对于AWS EC2上的应用,有每天2000封的免费额度,基本足够大部分人使用了。
当然,也要牢记,amazon不是专业的mail发送公司,涉及到更专业的送达服务、垃圾邮件等方面还不那么健全,普通用用是完全没问题的,毕竟大家都知道邮件是怎么回事,有点小问题也都能谅解,顶多勤快点,多泡泡amazon服务论坛。
二. SES接入模式
amazon提供了多种接入支持以满足各种不同的需要:
1. smtp发送服务,提供了STARTTLS、TLS Wrapper两种不同方式,端口不同,可以用于编程或安装应用软件配置
2. api发送服务,可以通过amazon的api发送邮件,这里提供了一个封装。https://github.com/pankratiev/python-amazon-ses-api/blob/master/amazon_ses.py3. 与 Postfix 整合,配置其relayhost参数,使已有的应用能方便的使用SES服务能力。
三. smtp接入案例
1. 发件人认证
SES需要对每一个发件人邮箱进行认证,不存在的发件人不能够发送邮件。只需要再verify a new sender里面输入邮件地址,SES会往这个邮箱发送一个验证URL,在浏览器打开该URL即可完成验证。
SES 工作在sanbox状态下,需要同时将收信人也进行同样的认证。如果需要在生产环境使用,需要事先申请,24小时开通。
2. 生成SMTP 用户密码对
在发信之前,还需要生成一个smtp用户密码对,只需要点击SMTP Settings 中的Create My SMTP Credentials按钮即可在IAM中生成一个账号,同时显示其名字、密码,记录下来保存好需要在发送邮件时使用。
3. 小程序
mailServer = smtplib.SMTP_SSL('email-smtp.us-east-1.amazonaws.com', 465)
mailServer.set_debuglevel(1)
mailServer.login(IAM用户名, IAM用户密码)
mailServer.sendmail('发件人', ['收件人'], msg.as_string())
mailServer.close()
Tuesday, March 20, 2012
Pyramid项目是怎么启动起来的?
Pyramid 官方文档上专门有一个章节解释了Pyramid项目的启动过程,不过因为整个启动过程还涉及到一些非pyramid模块,光看那个说明未必能很好理解清楚整个启动过程。下面,我们通过一个小例子简单看看一个Pyramid项目是怎么一步一步启动起来的。
一、创建好项目,配置好开发环境。
按照pyramid的介绍,我们用如下命令很快就可以创建一个演示项目。
pcreate -s alchemy PyramidKoans
python setup.py develop
Ok,现在就可以用
pserve development.ini来启动这个项目了。
二、pserve是啥?
问题来了,为什么是用pserve来启动?pserve又是怎么启动载入的呢?
我们知道,pyramid安装的时候会在/bin目录下(如果你用virtualenv的话)生成一堆的执行文件,pserve就是其中一个,我们打开看看先:
这堆文件基本都是这个样子,只是里面参数有差异而已,其实这些都是easy_install根据egg包中的egg_info自动生成的,所以当然都差不多啦。
下面我们再打开pyramid EGG_INFO中的entry_points.txt文件对比一下:
可以看到在/bin下生成的执行文件跟上面console_scripts是一一对应的。
所以,我们就可以很好理解 load_entry_point('pyramid==1.3b2', 'console_scripts', 'pserve')() 这语句了,它就是来pyramid 1.3b2这个egg包中来找'console_scripts', 'pserve'这个入口,然后载入其对应的程序,这里就是pyramid.scripts.pserve:main。
再打开pyramid包script目录下,找到pserve.py找到main函数,刚才的pserve命令就是启动了这个main函数而已。(其实这个script就是原来的paste script内容,基本原封不动的转到这里来了。)
二、paste deploy来了
既然用了paste script,那当然就要用到paste deploy啦。
找到PServeCommand类的run方法,很容易就可以找到
这就是用了paste deploy中的loadserver,loadapp方法。
paste deploy是一个很常用定位、配置WSGI应用/服务器的工具包,它可以从一个定制的配置文件里面载入你所定义的app和server。这里这个配置文件就是上面启动命令里面提到的development.ini。加载了app和server之后,马上可以看到
因为这里的server跟app都是符合WSGI标准的,所有将app丢给server即可启动啦。
三、development.ini
上面已经找到了server和app的载入,那么又是怎么找到你所需要的那个server跟app呢?要理解这点,就需要看development.ini这个配置文件了。
这是一个基本上算标准的ini配置文件,不过paste deploy还是在上面做了一些定制。比如server:main、app:main、use这些定义。
server:main
这里的server是一个固定用法,表示下面的配置用于启动一个WSGI server。
server:main这一配置块定义了WSGI server启动方式以及它的启动参数,如host、port。
这里的第一行use = egg:waitress#main就是定义了如何启动WSGI server。这里use也是个固定用法,egg:waitress#main表示找到waitress这个egg包,找到里面的entry_points中main定义项:
然后查看waitress这个包中的serve_paste这个方法即可,这个方法就是将waitress这个WSGI server启动起来。具体的waitress内容这里就不详细讨论了。
在定义waitressa参数的时候要注意下,我们经常还需要定义一个threads参数,它表示需要启动多少线程来提供服务,默认是4个,如果需要在正式环境用的话是不够的。
当然,我们也可以通过修改配置重新使用paste来替换waitress提供服务。
app:main
这里app也是固定用法,表示app_factory,表示由这里的信息来生成WSGI app。main是配置块名字,在使用pipeline的时候用得到。
app:main块定义了app中所能使用的参数(除了use),如pyramid.reload_templates、pyramid.debug_templates、sqlalchemy.url…这些都是pyramid本身或app程序中需要使用的配置,如果app需要定义自己的配置,也可以放在这里。
这里我们要着重看一下的是use = egg:PyramidKoans这句。egg:PyramidKoans表示从PyramidKoans这个egg包找到app的入口,这里省略了#main(默认就是它啦)。所以,又要找entry point啦,打开PyramidKoans项目中的setup.py文件(没打包发行之前,entry_points存在这里面。),
我们在这里也找到了entry_points,看[paste.app_factory]这里的定义,paste deploy就是来找这一段配置,再看main = pyramidkoans:main,这里第一个main就是我们刚才说的省略了的那个main,然后它指向了 pyramidkoans:main,表示需要到paramidkoans这个包里去找main这个方法。
终于走到我的程序了!
一、创建好项目,配置好开发环境。
按照pyramid的介绍,我们用如下命令很快就可以创建一个演示项目。
pcreate -s alchemy PyramidKoans
python setup.py develop
Ok,现在就可以用
pserve development.ini来启动这个项目了。
二、pserve是啥?
问题来了,为什么是用pserve来启动?pserve又是怎么启动载入的呢?
我们知道,pyramid安装的时候会在
这堆文件基本都是这个样子,只是里面参数有差异而已,其实这些都是easy_install根据egg包中的egg_info自动生成的,所以当然都差不多啦。
下面我们再打开pyramid EGG_INFO中的entry_points.txt文件对比一下:
可以看到在
所以,我们就可以很好理解 load_entry_point('pyramid==1.3b2', 'console_scripts', 'pserve')() 这语句了,它就是来pyramid 1.3b2这个egg包中来找'console_scripts', 'pserve'这个入口,然后载入其对应的程序,这里就是pyramid.scripts.pserve:main。
再打开pyramid包script目录下,找到pserve.py找到main函数,刚才的pserve命令就是启动了这个main函数而已。(其实这个script就是原来的paste script内容,基本原封不动的转到这里来了。)
二、paste deploy来了
既然用了paste script,那当然就要用到paste deploy啦。
找到PServeCommand类的run方法,很容易就可以找到
这就是用了paste deploy中的loadserver,loadapp方法。
paste deploy是一个很常用定位、配置WSGI应用/服务器的工具包,它可以从一个定制的配置文件里面载入你所定义的app和server。这里这个配置文件就是上面启动命令里面提到的development.ini。加载了app和server之后,马上可以看到
因为这里的server跟app都是符合WSGI标准的,所有将app丢给server即可启动啦。
三、development.ini
上面已经找到了server和app的载入,那么又是怎么找到你所需要的那个server跟app呢?要理解这点,就需要看development.ini这个配置文件了。
这是一个基本上算标准的ini配置文件,不过paste deploy还是在上面做了一些定制。比如server:main、app:main、use这些定义。
server:main
这里的server是一个固定用法,表示下面的配置用于启动一个WSGI server。
server:main这一配置块定义了WSGI server启动方式以及它的启动参数,如host、port。
这里的第一行use = egg:waitress#main就是定义了如何启动WSGI server。这里use也是个固定用法,egg:waitress#main表示找到waitress这个egg包,找到里面的entry_points中main定义项:
然后查看waitress这个包中的serve_paste这个方法即可,这个方法就是将waitress这个WSGI server启动起来。具体的waitress内容这里就不详细讨论了。
在定义waitressa参数的时候要注意下,我们经常还需要定义一个threads参数,它表示需要启动多少线程来提供服务,默认是4个,如果需要在正式环境用的话是不够的。
当然,我们也可以通过修改配置重新使用paste来替换waitress提供服务。
app:main
这里app也是固定用法,表示app_factory,表示由这里的信息来生成WSGI app。main是配置块名字,在使用pipeline的时候用得到。
app:main块定义了app中所能使用的参数(除了use),如pyramid.reload_templates、pyramid.debug_templates、sqlalchemy.url…这些都是pyramid本身或app程序中需要使用的配置,如果app需要定义自己的配置,也可以放在这里。
这里我们要着重看一下的是use = egg:PyramidKoans这句。egg:PyramidKoans表示从PyramidKoans这个egg包找到app的入口,这里省略了#main(默认就是它啦)。所以,又要找entry point啦,打开PyramidKoans项目中的setup.py文件(没打包发行之前,entry_points存在这里面。),
我们在这里也找到了entry_points,看[paste.app_factory]这里的定义,paste deploy就是来找这一段配置,再看main = pyramidkoans:main,这里第一个main就是我们刚才说的省略了的那个main,然后它指向了 pyramidkoans:main,表示需要到paramidkoans这个包里去找main这个方法。
终于走到我的程序了!
Wednesday, August 10, 2011
快速建立Pylons CI环境
因目前业务还跑在Pylons上,因此就拿Pylons来做这个例子吧。其他Python环境应该也是大同小异的。
CI系统环境:
OS: Ubuntu
CI 工具: Hudson 1.396
单元测试工具: nosetests
Hudson 本身的安装使用就不细写了,网上现在已经有很多了。直接下载war包,用java -jar运行即可。
启动之后,在管理界面中安装 Hudson Cobertura plugin、Python Plugin、Hudson Violations plugin、Subversion Plugin等插件即可。
一、建立虚拟环境
为了使Hudson同时支持多个python项目的持续集成,建议为每一个项目建立独立的虚拟环境,以便更清楚与项目相关联的包的情况,方便今后的部署。
下面几个步骤即可建立一个基本的虚拟环境。
1. 找到virtualenv包,取出其中的virtualenv.py
2. 运行python virtualenv.py --no-site-packages pylons-0.9.7 建立一个名字叫pylons-0.9.7的虚拟环境。这里 --no-site-packages参数是为了排除linux系统自带的python包对项目的影响,加了这个参数即可建立比较干净的Python虚拟环境。
3. 启动刚才建立的虚拟环境 source pylons-0.9.7/bin/activate
4. 运行easy_install nose 安装nosetests单元测试工具
5. 运行easy_install fudge 安装fudge Mock工具
6. 运行easy_install nosexcover 安装覆盖率生成工具,这个工具可以生成hudson中需要的xml格式的报表。仅仅安装coverage包不能生成类似报表。
二、建立项目运行环境
现在一个基本的环境已经基本建立完成了。下面在安装一些该项目特定的包
1. 安装mysql python包、sqlalchemy包
sudo apt-get install libmysqlclient-dev (MySQL-python编译安装需要mysql client开发环境)
easy_install MySQL-python
easy_install "sqlalchemy==0.5.8"
2. 安装pylons
easy_install -Z "pylons==0.9.7"
在pylons-0.9.7/lib/python26/site-packages/WebOb-1.1beta1-py2.6.egg/webob/__init__.py中加入一行from webob.multidict import UnicodeMultiDict
(pylons下载了最新的webob,里面有些改动,pylons 0.9.7不支持,需要改下)
3. 再按装写杂七杂八的项目用到的包
easy_install pycrypto
easy_install recaptcha_client
easy_install python-memcached
好了,一个pylons虚拟环境基本完成了,可以先直接下载项目代码运行nosetests看看有没有什么问题,如果运行都正常,即可进行以下步骤
三、建立Hudsong项目
1. 在Hudson中新建一个任务,选择“构建一个自由风格的软件项目“
2. 输入代码地址(SVN/CVS...)
3. 按照crontab规则建立build规律
4. 选择shell脚本作为build命令,在里面输入如下内容:
. /home/mm/pylons-0.9.7/bin/activate
cd ${WORKSPACE}
nosetests --cover-package=包名 --cover-tests --with-xcoverage --with-xunit --verbose
注:这里报名替换成需要测试覆盖率的包名
5. 设置输出报表
Publish JUnit test result report
xml文件直接输入 nosetests.xml
Publish Cobertura Coverage Report
xml文件直接输入 coverage.xml
CI系统环境:
OS: Ubuntu
CI 工具: Hudson 1.396
单元测试工具: nosetests
Hudson 本身的安装使用就不细写了,网上现在已经有很多了。直接下载war包,用java -jar运行即可。
启动之后,在管理界面中安装 Hudson Cobertura plugin、Python Plugin、Hudson Violations plugin、Subversion Plugin等插件即可。
一、建立虚拟环境
为了使Hudson同时支持多个python项目的持续集成,建议为每一个项目建立独立的虚拟环境,以便更清楚与项目相关联的包的情况,方便今后的部署。
下面几个步骤即可建立一个基本的虚拟环境。
1. 找到virtualenv包,取出其中的virtualenv.py
2. 运行python virtualenv.py --no-site-packages pylons-0.9.7 建立一个名字叫pylons-0.9.7的虚拟环境。这里 --no-site-packages参数是为了排除linux系统自带的python包对项目的影响,加了这个参数即可建立比较干净的Python虚拟环境。
3. 启动刚才建立的虚拟环境 source pylons-0.9.7/bin/activate
4. 运行easy_install nose 安装nosetests单元测试工具
5. 运行easy_install fudge 安装fudge Mock工具
6. 运行easy_install nosexcover 安装覆盖率生成工具,这个工具可以生成hudson中需要的xml格式的报表。仅仅安装coverage包不能生成类似报表。
二、建立项目运行环境
现在一个基本的环境已经基本建立完成了。下面在安装一些该项目特定的包
1. 安装mysql python包、sqlalchemy包
sudo apt-get install libmysqlclient-dev (MySQL-python编译安装需要mysql client开发环境)
easy_install MySQL-python
easy_install "sqlalchemy==0.5.8"
2. 安装pylons
easy_install -Z "pylons==0.9.7"
在pylons-0.9.7/lib/python26/site-packages/WebOb-1.1beta1-py2.6.egg/webob/__init__.py中加入一行from webob.multidict import UnicodeMultiDict
(pylons下载了最新的webob,里面有些改动,pylons 0.9.7不支持,需要改下)
3. 再按装写杂七杂八的项目用到的包
easy_install pycrypto
easy_install recaptcha_client
easy_install python-memcached
好了,一个pylons虚拟环境基本完成了,可以先直接下载项目代码运行nosetests看看有没有什么问题,如果运行都正常,即可进行以下步骤
三、建立Hudsong项目
1. 在Hudson中新建一个任务,选择“构建一个自由风格的软件项目“
2. 输入代码地址(SVN/CVS...)
3. 按照crontab规则建立build规律
4. 选择shell脚本作为build命令,在里面输入如下内容:
. /home/mm/pylons-0.9.7/bin/activate
cd ${WORKSPACE}
nosetests --cover-package=包名 --cover-tests --with-xcoverage --with-xunit --verbose
注:这里报名替换成需要测试覆盖率的包名
5. 设置输出报表
Publish JUnit test result report
xml文件直接输入 nosetests.xml
Publish Cobertura Coverage Report
xml文件直接输入 coverage.xml
Subscribe to:
Posts (Atom)