Monday, April 2, 2012

Pyramid中的view(下)


三. view配置案例

1. 专用于POST,且有read权限才能访问地view配置
@view_config(route_name='ok', request_method='POST', permission='read')

2. 置于类定义之上地view配置
@view_config(route_name='hello')
class MyView(object):
    def __init__(self, request):
        self.request = request

    def __call__(self):
        return Response('hello')

3. 两个view配置堆叠,分别起效
@view_config(route_name='edit')
@view_config(route_name='change')
def edit(request):
    return Response('edited!')

4. 类中方法上地view配置
class MyView(object):
    def __init__(self, request):
        self.request = request

    @view_config(route_name='hello')
    def amethod(self):
        return Response('hello')

5. 不用__call__作为默认方法的配置
@view_config(attr='amethod', route_name='hello')
class MyView(object):
    def __init__(self, request):
        self.request = request

    def amethod(self):
        return Response('hello')

6. 使用view_defaults简化配置参数
@view_defaults(route_name='rest')
class RESTView(object):
    def __init__(self, request):
        self.request = request

    @view_config(request_method='GET')
    def get(self):
        return Response('get')

    @view_config(request_method='POST')
    def post(self):
        return Response('post')

    @view_config(request_method='DELETE')
    def delete(self):
        return Response('delete')

7. 这样也是可以简化的
@view_defaults(route_name='rest')
class RESTView(object):
    def __init__(self, request):
        self.request = request

    def get(self):
        return Response('get')

    def post(self):
        return Response('post')

    def delete(self):
        return Response('delete')

if __name__ == '__main__':
    config = Configurator()
    config.add_route('rest', '/rest')
    config.add_view(RESTView, attr='get', request_method='GET')
    config.add_view(RESTView, attr='post', request_method='POST')
    config.add_view(RESTView, attr='delete', request_method='DELETE')


8. 类继承也可以view_defaults!
@view_defaults(route_name='rest')
class Foo(object):
    pass

class Bar(Foo):
    pass
这时,类Bar也继承了Foo的view配置。如果一定要取消这种继承,则用如下代码:
@view_defaults(route_name='rest')
class Foo(object):
    pass

@view_defaults()
class Bar(Foo):
    pass


三、view中的exception处理

1. 异常

在view的开发过程中,经常会需要抛出一些异常,比如没有权限,404,禁止访问等。Pyramid专门定义了一系列专用的Exception类用于这种情况。所有exception类都继承自pyramid.httpexceptions.HTTPException。从实现角度来看,这些类其实是一些特殊的Response。因此,可以在程序种直接return、或raise。比如
def aview(request):
    raise HTTPUnauthorized()

def aview(request):
    return HTTPUnauthorized()

甚至可以:
def aview(request):
    raise exception_response(401)

2. 特殊的异常

Pyramid有几个比较特殊的exception:

pyramid.httpexceptions.HTTPNotFound,当Pyramid找不到view时抛出HTTPNotFound,并将NotFound view返回给浏览器。可以在启动时通过config.add_notfound_view(not found)来变更这个view。

pyramid.httpexceptions.HTTPForbidden,当授权没有通过时,抛出HTTPForbidden,并将Forbidden view返回给浏览器。可以在启动时通过config.add_forbidden_view(forbidden_view)来变更这个view。

pyramid.httpexceptions.HTTPFound,发起302 Found应答,实现URL重定向功能,如raise HTTPFound(location='http://example.com')

3. 定制异常视图

用户可以自定义异常,那么捕获自定义异常之后,该如何展现这个异常呢?Pyramid专门提供了异常view这种处理机制,如我们定义了一个异常:
class ValidationFailure(Exception):
    def __init__(self, msg):
        self.msg = msg

那么,我们可以相对应的定义一个这样的view:
@view_config(context=ValidationFailure)
def failed_validation(exc, request):
    response =  Response('Failed validation: %s' % exc.msg)
    response.status_int = 500
    return response

这样,一旦程序抛出ValidationFailure这个异常,Pyramid将调用这个view,并将其response返回给浏览器。

五、表单数据处理

可以通过request.params取得所有form提交的参数。如

def myview(request):
    firstname = request.params['firstname']
    lastname = request.params['lastname']

注意,在URL分发方式下,还有一个matchdict里面包含了一部分的URL路径参数。

Saturday, March 31, 2012

Pyramid中的view(上)


本节资料整理改编自Pyramid官方网站,部分内容按理解进行了增删整理和归纳,以便结构上更清晰。
不知道官网为啥要将view、view configuration划分成独立的两个章节,不管它了。

一、如何定义view
在Pyramid中,view是一个可调用的python对象,可以是方法,也可以是类。它基本等同与原来Pylons中的controller。每个view至少需要接收一个request参数,并最终形成一个Response对象。
1. 函数方式定义的view
from pyramid.response import Response
def hello_world(request):
    return Response(’Hello world!’)

2. 类方式定义的view
from pyramid.response import Response
class MyView(object):
def __init__(self, request):
    self.request = request
def __call__(self):
    return Response(’hello’)
注意这里__init__、__call__两个函数的参数,其中__init__中的request即函数方式定义view的request。

另外需要注意的是,view的参数也有可能是context, request两个(在traversal中),如:
def view(context, request):
        return Response('OK')

class view(object):
        def __init__(self, context, request):
                self.context = context
                self.request = request

        def __call__(self):
                return Response('OK')

二、如何配置view
上面的例子只是定义了一个view,其需要生效可用还需要经过配置这一关。这些配置信息直接用于view的定位与判定。
正如在早先的章节中说明的,view配置有两种方式,一种是add_view方法,另外一种是view_config标注。两者的定义是可以完全等价的,其参数也基本一致,只是后者需要一个scan操作查找这些配置。例如:
config.add_view(’mypackage.views.my_view’, route_name=’ok’, request_method=’POST’, permission=’read’)

@view_config(route_name=’edit’)
def edit(request):
    return Response(’edited!’)

开发者通过配置使其开发的view能在Pyramid应用中使用。view配置参数大都来自context 和request。


跟route配置参数类似,view配置参数也可以分成普通参数、断言参数。在view的查找过程中,断言参数扮演了一个很重要的角色。一个view配置中断言参数的数量越多,调用该view所需要的环境就会越细化。一个申明了6个断言参数的view总是在申明了2个断言参数的view之前被查找、评估。必须所有的断言参数都匹配,该view才能被调用。(这跟route中的断言参数的作用是不一样的。)

1. 普通参数:
permission: 该view的访问权限,这个后续会具体介绍。
attr: Pyramid默认调用的是view类的__call__函数,如果需要指定调用其他方法,通过attr指定。如attr='index'。
renderer: 指定构建Response的渲染器。如json,模版等。后续章节详细介绍。
http_cache: 指定Response地expires和Cache-Control头属性。设置该值基本等同调用response.cache_expires。如
        http_cache=3600,表示通知浏览器缓存1小时、
        http_cache=datetime.timedelta(days=1),表示通知浏览器缓存1天、
        http_cache=0,表示无缓存
        http_cache=(3600, {'public':True}),表示缓存1小时,并且response.cache_control.public = True.
wrapper: 串联view以构造更复杂地Response。
decorator: view地装饰器,该装饰器需要返回一个接受context, request参数的view
mapper: 指定view mapper,用与转换view地参数和返回值。

2. 断言参数
name: view名字,在漫游时使用
context: 上下文,可以是对象或接口,也主要在漫游时使用
route_name: route名,主要用于URL分发。
request_type: 指定需要符合地request地接口,不常用。
request_method: GET、POST、DELETE、HEAD
request_param: 指定GET、POST必含的参数,如使用了request_param="foo=123"这种方式,则必须参数名、参数值都对应才算匹配。
match_param: match中必须包含地参数,如使用了request_param="foo=123"这种方式,则必须参数名、参数值都对应才算匹配。如果是一个字典,则必须里面每一项都匹配。
containment: resource树包含关系,必须是参数地子孙节点才匹配。
xhr: 匹配时是否处理HTTP_X_REQUESTED_WITH
header: 指定请求中必含的的HTTP header或header名值对。如‘User-Agent:Mozilla/.*’、'Host:localhost'
accept: 指定HTTP 请求头中客户端可以能够接受的内容类型,如'text/plain'、'text/*'、'*/*'
path_info: 匹配PATH_INFO的正则表达式。
custom_predicates: 定制的断言可执行对象。

Friday, March 30, 2012

使用Traversal来配置Pyramid项目(五)URL分发、漫游的混合配置



五、URL分发、漫游的混合配置

除了单独使用URL分发、漫游两种方式之外,Pyramid还提供了混合模式,可以将两者结合在一起使用。在混合模式下,漫游只发生在当某条route规则匹配之后才会发生。

跟纯粹的基于漫游地应用对比,两者地区别在于:
 * 纯粹漫游方式没有route定义;混合模式至少有一个。
 * 纯粹漫游方式root resource 是全局地,通过root factory在启动时创建;混合模式的root resource是基于每条route定义的。
 * 纯粹漫游方式漫游路径是整个PATH_INFO;混合模式是按route定义中的匹配字段匹配之后的一部分。
 * 纯粹漫游方式view配置不需要指明route_name,view定位时也不需要考虑它;混合模式则需先匹配route_name。


1. 混合模式

在add_route定义时:
 * pattern参数包含一个特殊的动态内容:*traverse 或 *subpath。
 * factory参数指向一个特定的root factory。
 * 如果没有factory参数,系统则采用全局的root factory(Configurator创建时传入)创建root对象。
 * 如果没有全局的root factory,则采用默认的root factory。


2. 使用*traverse定义混合模式

例如:
config.add_route('home', '{foo}/{bar}/*traverse')
config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory')

我们这里还是采用traversal案例中的root_factory,来看看下面的URL:
在这里例子中,漫游路径就是'a','b', 'c'

如果我们定义如下view配置:
config.add_view('mypackage.views.myview', route_name='home')
则mypackage.views.myview将在以下条件下被调用:
 * 匹配'home'为route名字
 * 漫游之后的view名字为空
 * context为任意对象

我们再来看下面的片段:
config.add_route('home', '{foo}/{bar}/*traverse',
                 factory='mypackage.routes.root_factory')
config.add_view('mypackage.views.myview', route_name='home')
config.add_view('mypackage.views.another_view', route_name='home',
                name='another')
则mypackage.views.another_view将在以下条件下被调用:
 * 匹配'home'为route名字
 * 漫游之后的view名字为another
 * context为任意对象
http://example.com/one/two/a/another URL可以访问another view


3. 使用traverse参数定义混合模式

使用*traverse定义的话,只能将URL最后的部分作为漫游序列。如果想要更灵活呢?Pyramid还引入了一个traverse参数。

例如:
config.add_route('abc', '/articles/{article}/edit', traverse='/{article}')

这里traverse参数的语法跟pattern是一样的,而且traverse中地内容必须完全包含在pattern中。

如上例,访问URL /articles/1/edit,则article匹配之后的值是1。因此,漫游路径就是‘/1’。即在root 对象上以‘1’为参数调用__getitem__,如果存在1这个对象,则将其作为context,传入view。

如果pattern中有*traverse定义,则忽略traverse参数。

4. 使用 *subath

如果想在route匹配时使用subpath,但又不想去执行漫游(漫游会产生subpath这个参数)。那么我们可以在pattern中使用subpath。
from pryamid.static import static_view

www = static_view('mypackage:static', use_subpath=True)

config.add_route('static', '/static/*subpath')
config.add_view(www, route_name='static')


Thursday, March 29, 2012

使用Traversal来配置Pyramid项目(四)复杂Traversal案例

四、一个复杂的案例

models.py
---------------------------
from sqlalchemy import (
    Column,
    Integer,
    Text,
    and_,
    )

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

class MyCat(Base):
    __tablename__ = 'mycat'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    pid = Column(Integer)
    desc = Column(Text)

    def __init__(self, name, pid, desc):
        self.name = name
        self.pid = pid
        self.desc = desc
        
    def __getitem__(self, key):
        session= DBSession()
        
        is_file = False
        try:
            key.index(".")
            is_file = True
        except: pass
        
        if is_file:
            item = session.query(MyFile).filter(and_(MyFile.name==key, MyFile.cat==self.id)).first()
        else:
            item = session.query(MyCat).filter(and_(MyCat.name==key, MyCat.pid==self.id)).first()

        if item is None:
            raise KeyError(key)

        item.__parent__ = self
        item.__name__ = key
        return item

    def get(self, key, default=None):
        try:
            item = self.__getitem__(key)
        except KeyError:
            item = default
        return item

    def listall(self):
        session= DBSession()
        cats = session.query(MyCat).filter(MyCat.pid==self.id).all()
        files = session.query(MyFile).filter(MyFile.cat==self.id).all()
        return cats + files

class MyFile(Base):
    __tablename__ = 'myfile'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    cat = Column(Integer)
    save_path = Column(Text)
    desc = Column(Text)

    def __init__(self, name, cat, save_path, desc):
        self.name = name
        self.cat = cat
        self.save_path = save_path
        self.desc = desc


class MyRoot(object):
    __name__ = None
    __parent__ = None

    def __getitem__(self, key):
        session= DBSession()

        item = session.query(MyCat).filter(and_(MyCat.name==key, MyCat.pid==0)).first()
        if item is None:
            raise KeyError(key)

        item.__parent__ = self
        item.__name__ = key
        return item

    def get(self, key, default=None):
        try:
            item = self.__getitem__(key)
        except KeyError:
            item = default
        return item

    def __iter__(self):
        session= DBSession()
        query = session.query(MyCat).filter(MyCat.pid==0)
        return iter(query)

root = MyRoot()

def root_factory(request):
    return root



views.py
--------------------------
from .models import (
    DBSession,
    MyCat,
    MyFile,
    )

def view_root(context, request):
    print request.resource_url(context)
    return {'context':context, 'items':list(context), 'project':'MyTest'}

def view_cat(context, request):
    print request.resource_url(context)
    return {'context':context, 'items':context.listall(), 'project':'MyTest'}

def view_file(context, request):
    print request.resource_url(context)
    return {'item':context, 'project':'MyTest'}

def view_photo(context, request):
    return {'item':context, 'project':'MyTest'}


__init__.py
--------------------------
from pyramid.config import Configurator
from sqlalchemy import engine_from_config

from .models import (
    DBSession, 
    root_factory,
    )

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    config = Configurator(settings=settings, root_factory=root_factory)
    config.add_static_view('static', 'static', cache_max_age=3600)


    config.add_view('traverseonrdb.views.view_root', 
                    context='traverseonrdb.models.MyRoot', 
                    renderer="templates/root.pt")
    config.add_view('traverseonrdb.views.view_cat', 
                    context='traverseonrdb.models.MyCat', 
                    renderer="templates/cat.pt")

    config.add_view('traverseonrdb.views.view_photo',
                    name="photoview",
                    context='traverseonrdb.models.MyFile',
                    renderer="templates/photo.pt")    
    
    config.add_view('traverseonrdb.views.view_file',
                    context='traverseonrdb.models.MyFile',
                    renderer="templates/file.pt")

    return config.make_wsgi_app()


populate.py
--------------------------
# -*- coding: UTF-8 -*-

import os
import sys
import transaction

from sqlalchemy import engine_from_config

from pyramid.paster import (
    get_appsettings,
    setup_logging,
    )

from ..models import (
    DBSession,
    MyCat,
    MyFile,
    Base,
    )

def usage(argv):
    cmd = os.path.basename(argv[0])
    print('usage: %s <config_uri>\n'
          '(example: "%s development.ini")' % (cmd, cmd)) 
    sys.exit(1)

#直接在数据库中构建如下初始数据以供演示。
#MySite ---- MyPhoto ---- SportPhoto ---- ski.jpg
#         |            |
#         |            -- PrivatePhoto ---- love.jpg
#         |
#         -- MyNote ---- 20120327.txt
#         |
#         -- MyMP3 ---- paradise.mp3
#         |
#         -- MyVideo ---- hot.avi
def main(argv=sys.argv):
    if len(argv) != 2:
        usage(argv)
    config_uri = argv[1]
    setup_logging(config_uri)
    settings = get_appsettings(config_uri)
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.create_all(engine)
    with transaction.manager:
        my_photo_cat = MyCat(name='MyPhoto', pid=0, desc="photos taken by myself")
        DBSession.add(my_photo_cat)
        my_note_cat = MyCat(name='MyNote', pid=0, desc="take a note a day to save life")
        DBSession.add(my_note_cat)
        my_mp3_cat = MyCat(name='MyMP3', pid=0, desc="my favorite mp3")
        DBSession.add(my_mp3_cat)
        my_video_cat = MyCat(name='MyVideo', pid=0, desc="all my hot videos")
        DBSession.add(my_video_cat)
        DBSession.flush()
        
        my_sport_photo_cat = MyCat(name='SportPhoto', pid=my_photo_cat.id, desc="photos of sports")
        DBSession.add(my_sport_photo_cat)
        my_private_photo_cat = MyCat(name='PrivatePhoto', pid=my_photo_cat.id, desc="secret photos")
        DBSession.add(my_private_photo_cat)
        DBSession.flush()
        
        afile = MyFile(name='ski.jpg', cat=my_sport_photo_cat.id, save_path="", desc="20111112 by canon")
        DBSession.add(afile)
        afile = MyFile(name='love.jpg', cat=my_private_photo_cat.id, save_path="", desc="20111111 by canon")
        DBSession.add(afile)
        afile = MyFile(name='20120327.txt', cat=my_note_cat.id, save_path="", desc="diary 20120327")
        DBSession.add(afile)
        afile = MyFile(name='paradise.mp3', cat=my_mp3_cat.id, save_path="", desc="mp3 download")
        DBSession.add(afile)
        afile = MyFile(name='hot.avi', cat=my_video_cat.id, save_path="", desc="hot video from some place")
        DBSession.add(afile)


详细代码参见:
https://github.com/eryxlee/pyramid_koans/tree/master/traverseonrdb

Wednesday, March 28, 2012

使用Traversal来配置Pyramid项目(三)Traversal案例

三、案例
1. 漫游示例



假如我们需要访问一个链接http://example.com/foo/bar/baz/biz/buz.txt。那么这个访问请求的PATH_INFO即为/foo/bar/baz/biz/buz.txt。
同时假如我们有如下的resource tree

整个漫游过程如下:
- 找到root resource,查询foo
- 找到foo resource,查询bar
- 找到bar resource,查找baz,找不到,抛出KeyError
得到 context 为bar resource
view 名 为 baz
subpath 为 (u'biz', u'buz.txt')
随即,漫游结束,开始view定位。

下面我们将resource树改成如下形状:

访问同样的链接http://example.com/foo/bar/baz/biz/buz.txt
整个漫游过程如下:
- 找到root resource,查询foo
- 找到foo resource,查询bar
- 找到bar resource,查询baz
- 找到baz resource,查询biz
- 找到biz resource,查找bux.txt,找不到,抛出KeyError异常
得到context为biz resource
view名为buz.txt
subpath为()

2. 完整案例

上例中:
5-6行,创建了一个简单的resouce类
8-9行,创建了一个resource 树
11-13行,定义了一个view
16行,创建带root_factory的configurator
17行,将view加入注册配置,并指定上下文为Resource类

下面我们就可以启动这个程序,然后访问如下URL:
http://localhost:8080/,浏览器即显示Here's a resource and its children: {'a': {'b': {'c': {}}}}
http://localhost:8080/a/b,浏览器即显示Here's a resource and its children: {'c': {}}
http://localhost:8080/a/b/c,浏览器即显示Here's a resource and its children: {}

http://localhost:8080/xyz,浏览器即显示 404 Not Found
http://localhost:8080/a/b/c/d,浏览器即显示 404 Not Found

3. 进一步改变
我们删去resource树的构建,让系统使用默认的root_factory,则我们可以得到下面的例子:

现在我们再访问如下URL
http://localhost:8080/foobar,浏览器显示Here's a simple view without any route/resource configuration.
这是一个traversal地变种,只有root resource,而且foobar是view名字,这种情况比较适合做简单地小程序来测试熟悉系统。

Tuesday, March 27, 2012

使用Traversal来配置Pyramid项目(二)Traversal算法

二、Traversal

1. root factory配置

理解清楚resource树之后,我们就可以自己按照需要构建定制的resource树,并在Pyramid项目中的__init__模块的main函数中将这棵树的根节点传给Pyramid配置方法Configurator。如果调用该方法的时候,没有root_factory参数或root_factory参数为None,系统会使用一个默认的root_factory,它永远返回一个不带子节点的resource。

2. 算法参考案例
class Resource(dict):
pass

def get_root(request):
return Resource({'a': Resource({'b': Resource({'c': Resource()})})})

可能地访问URL:/a/b/c

3. traversal算法

当一个用户对一个使用了Traversal算法的应用发起一次请求时,系统采用了如下算法来找到一个上下文resource和view名字。

1) request请求通过WSGI服务器,形成标准的WSGI请求,传递给router程序。
2) router 根据WSGI环境变量构造一个request对象。
3) 调用root_factory(get_root),得到root resource。
4) router采用WSGI中的PATH_INFO变量作为traverse(漫游)的路径。首先,去掉前导的'/',并将后续的路径片段按'/'切分形成一个漫游序列,如[u'a', u'b', u'c']。 注意我们这里的内容都是经过url unquote以及unicode decode的,所有数据值都是以unicode方式存在的。
5) 现在我们有一个root resource,以及一个漫游序列。我们从漫游序列中取出一个数据:'a',并将它传给root resource地__getitem__方法,我们可以得到另外一个resource(我们就叫它resource ‘A')。然后再从漫游序列中取出一个数据:'b',然后传给resource 'A'的__getitem__方法,我们又得到了一个新地resource(resource 'B')。再从漫游序列取出一个数据:’c',传给resource 'B'的__getitem__方法,则得到resource 'C'。
6) 一旦整个漫游序列用完,或任何一次__getitem__调用抛出一个KeyError异常、AttributeError异常的时候,以及遇到任何一个以@@开头的路径片段时,漫游过程即中止。
7) 不论以上任何一种情况终止了漫游,最后一个发现的resource即为我们需要的上下文(context)。如果这个时候漫游序列耗尽了,则view名字为‘’。否则紧邻的一个路径片段将被当作view名字。
8) 在view名字之后的任何路径片段极为subpath。

一旦得到了context,view名,subpath,漫游的所有任务即结束了,这些参数将传递给router,以便进行下一步的view定位工作。

使用Traversal来配置Pyramid项目(一)Resources

本节资料整理改编自Pyramid官方网站,部分内容按理解进行了增删整理和归纳,以便结构上更清晰。
Pyramid 1.3官方文档对于Traversal、resource、view等的说明比较分散凌乱,结构上不是很清晰(个人感觉),因此这里重新编排这部分内容,如果有人不能认同,请参阅Pyramid官方网站(http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/index.html)。

在当前网站建设中,我们经常可以看到网站地图这样一个功能,通过层级关系,将网站功能逐次列出来,其实我们仔细看的话,我们可以发现,严格来说,网站地图(特别是静态网站)也是一个树状结构的图,通过一层一层的目录结构形成一个URL,如http://127.0.0.1/photos/sports/1123.jpg,http://127.0.0.1/photos/sports/1120.jpg,http://127.0.0.1/photos/tour/1212.jpg,我们将这些URL按'/'分解,就可以看到,每一个URL其实就是在这颗树中从根到叶子节点的一次查询过程。

这就是Traversal基本思想的来源。这棵树中的所有节点就是Traverse所用到的resource。

一、Resources

1. 什么是resource

在Pyramid中,resource就是指一个app对应的结构树中的一个节点。在Pyramid中,即便从不使用,也会默认生成一个root resource。这个root节点就是这个resource树的根节点。一棵resource树就是用于表达网站结构一个类似递归字典结构。

如果使用Traversal方式,Pyramid将会根据PATH_INFO由根开始查找这棵resource树,直到找到一个resource(PATH用完或找不到子节点),Pyramid即肯定该resource为上下文(context),并用这个context和request中其他数据来定位一个合适的view。

如果使用URL分发方式,通常开发者将不会直接接触到resource树。在这种模式下,resouce树通常只有一个根节点,用来保存一些安全认证信息。

在很多情况下,参与resource树构建的不仅仅只有网站结构信息,还会有网站本身数据模型的数据。

根据树的定义,我们可以将resource分成容器节点、叶子节点两种。

针对Pyramid这种类似递归字典的数据结构,也可以表示为容器节点必须带__getitem__方法,通过调用这个方法传入名字可以得到对应的子节点,如果传入的名字查找不到相应的子节点,必须抛出KeyError异常。而叶子节点则没有__getitem__方法,即便有,也必须总是抛出KeyError异常。

按照上面的定义,我们可以简单构造如下resource树:
class Resource(dict):
pass

def get_root(request):
return Resource({'a': Resource({'b': Resource({'c': Resource()})})})


在这个例子中,get_root就是一个root_factory,它返回一个Resurce,我们可以命名它为root,那么root['a']就是一个容器节点,里面包含一个key为'b'的子节点root['a']['b'],这个root['a']['b']也是一个容器节点,里面包含一个key为'c'的子节点root['a']['b']['c'],而root['a']['b']['c']则是一个叶子节点。

如果我们将这个get_root作为Pyramid Configurator的root_factory参数,那么当一个URL访问如/a/b/c来临时,Traversal将找到这个key为'c'的对象,并用它与request来定位view。

在这里例子中,为了简化程序,我们将resource树中的所有节点都定义成了同一种类型,在真实环境中,每一个节点都可以是任意类型。


2. 节点的位置感知

为了URL生成,定位,安全以及traversal API等因素的需要,Pyramid规定resource树中的所有resource必须是可感知位置的,即每一个节点必须带__parent__、__name__两个属性。

__parent__属性永远指向该节点的父节点。__name__则是一个名字以供其父节点通过__getitem__函数来查找。

root resource中的__name__必须为空字串,__parent__为None。

如一个resource从root resource中通过__getitem__返回,则该resource的__parent__必须指向root resource,__name__必须跟调用__getitem__是的参数一致。即从任何一个节点都可以通过递归__parent__得到root resource。


3. 通过resource生成URL

一旦每一个resource都可以感知位置之后,我们就可以通过pyramid.request.Request.resource_url()来生成访问该resource的URL。
如root resource有一个名字为a的子节点resource_a,那么调用request.resource_url(resource_a)即可生成http://example.com/a/这样一个URL。注意,这里产生的URL最后带一个'/'。这是因为resource指代的是resource 树这个层级结构中的一个位置。

resource_url也可以带参数,如request.resource_url(resource_a, 'foo', 'bar')可以生成http://example.com/a/foo/bar这样的URL。注意,这里最后是不带'/'的。

或者传入query参数,如request.resource_url(resource_a, query={'a':'1'})可以生成http://example.com/a/?a=1


4. 跟resouce相关的一些api

pyramid.traversal.resource_path(resource) 返回不带域名的URL

pyramid.traversal.find_resource(resource, '/path') 通过path找到resource,可以使用绝对路径(前带'/'),或相对路径

pyramid.location.lineage() 产生一个包含其自身及依次父节点的生成器

pyramid.location.inside(b, a) 检查b是否是a的子孙。

pyramid.traversal.find_root() 找到根节点

pyramid.traversal.find_interface() 通过接口查找resource