结合遍历和URL调度

当你写得最多的时候 Pyramid 应用程序,您将使用两个可用的应用程序中的一个 resource location 子系统:遍历或URL调度。但是,为了解决一组有限的问题,使用 both 遍历和URL在同一应用程序中一起调度。 Pyramid 通过 混合的 应用。

警告

对于“混合”的URL调度+遍历应用程序的行为进行推理可能具有挑战性。要成功地将URL调度和遍历一起使用,您需要了解URL模式匹配、根工厂和 traversal 算法,以及它们之间潜在的交互作用。因此,我们不建议创建依赖混合行为的应用程序,除非必须这样做。

非混合应用综述

根据其文档中的教程使用时, Pyramid 是一个“双模式”框架:教程解释了如何使用 URL dispatch or traversal . 本章详细介绍了如何组合这两种调度机制,但在尝试组合它们之前,我们将回顾它们是如何独立工作的。

仅限URL调度

使用 URL dispatch 专门用于将URL映射到代码的语句通常在其应用程序启动配置中包含如下语句:

1
2
3
4
5
6
7
# config is an instance of pyramid.config.Configurator

config.add_route('foobar', '{foo}/{bar}')
config.add_route('bazbuz', '{baz}/{buz}')

config.add_view('myproject.views.foobar', route_name='foobar')
config.add_view('myproject.views.bazbuz', route_name='bazbuz')

route 对应于一个或多个视图可调用文件。通过传递 route_name 调用期间与其名称匹配的参数 add_view() . 在请求期间匹配路由时, view lookup 用于将请求与其关联的可调用视图匹配。有人打电话给 add_route() 表示应用程序正在使用URL调度。

仅遍历

仅使用遍历的应用程序将具有如下所示的视图配置声明:

1
2
3
4
# config is an instance of pyramid.config.Configurator

config.add_view('mypackage.views.foobar', name='foobar')
config.add_view('mypackage.views.bazbuz', name='bazbuz')

当将上述配置应用于应用程序时, mypackage.views.foobar 当URL /foobar 参观。同样,这个观点 mypackage.views.bazbuz 当URL /bazbuz 参观。

通常,专门使用遍历的应用程序不会执行对 pyramid.config.Configurator.add_route() 在它的启动代码中。

混合应用

可以单独使用遍历或URL调度来创建 Pyramid 应用。然而,在构建应用程序时,也可以将遍历和URL调度的概念结合起来,其结果是混合应用程序。在混合应用程序中,执行遍历 之后 一条特定的路线已经匹配。

混合应用程序更像是基于“纯”遍历的应用程序,而不是基于“纯”URL调度的应用程序。但与基于“纯”遍历的应用程序不同,在混合应用程序中 traversal 在路由匹配后的请求期间执行。这意味着表示 pattern 路由的参数必须与 PATH_INFO 在路由模式匹配之后,大多数关于 resource locationview lookup 申请。

纯遍历应用程序和混合应用程序之间只有四个真正的区别:

  • 在纯基于遍历的应用程序中,没有定义路由。在混合应用程序中,将至少定义一个路由。
  • 在纯基于遍历的应用程序中,使用的根对象是全局的,由 root factory 在启动时提供。在混合应用程序中, root 开始遍历的对象可以根据每个路由进行更改。
  • 在纯基于遍历的应用程序中, PATH_INFO 潜在的 WSGI 环境被批量用作遍历路径。在混合应用程序中,遍历路径不是完整的 PATH_INFO 字符串,但由匹配路由配置模式中的匹配模式确定的URL的一部分。
  • 在纯基于遍历的应用程序中,查看不提到 route_nameview lookup . 在混合应用程序中,当路由匹配时,只查看将该路由的名称作为 route_nameview lookup .

一般来说,混合应用程序 is 基于遍历的应用程序,除了:

  • 遍历 root 根据匹配的路由的路由配置选择,而不是从 root_factory 在应用程序启动配置期间提供。
  • 遍历 path 是根据匹配的路由的路由配置选择的,而不是从 PATH_INFO 请求的
  • view lookup 当路由匹配仅限于特定名称的路由时 route_name 在它们的配置中,与匹配的路由相同 name .

要创建混合模式应用程序,请使用 route configuration 这意味着 root factory 其中还包括 pattern 包含特殊动态部分的参数:或者 *traverse*subpath .

路由匹配的根对象

混合应用程序意味着在路由匹配之后的请求期间执行遍历。根据定义,遍历必须始终从根对象开始。因此,了解 哪一个 路由匹配后将遍历根对象。

找出 root 特定路由匹配的对象结果很简单。匹配路由时:

  • 如果路由的配置具有 factory 指向a的参数 root factory Callable,将调用该Callable以生成 root 对象。
  • 如果路由的配置没有 factory 论点 全球的 root factory 将被调用以生成 root 对象。全局根工厂是 root_factory 传递给的参数 Configurator 在应用程序启动时。
  • 如果A root_factory 参数未提供给 Configurator 启动时,A 违约 使用根工厂。默认根工厂用于生成根对象。

注解

与路线相关的根工厂在前面的 路由工厂 . 全局根工厂和默认根工厂在前面的 资源树 .

使用 *traverse 以路线模式

混合应用程序通常意味着包含特殊令牌的路由配置 *traverse 在路线模式的末端:

1
config.add_route('home', '{foo}/{bar}/*traverse')

A *traverse 路由配置中模式末尾的标记表示“余数” 捕获 价值。当它被使用时,它将匹配URL的其余路径段。其余部分将成为用于执行遍历的路径。

注解

这个 *remainder 路由模式语法在 路由模式语法 .

混合模式应用程序更依赖于 traversalresource locationview lookup 比大多数例子所表明的 URL调度 .

因为上述路线的模式以 *traverse ,在请求期间匹配此路由配置时, Pyramid 将尝试使用 traversal 反对 root 对象所隐含的 root factory 这是路由配置所暗示的。因为没有 root_factory 为该路由显式指定了参数,这将是 全球的 应用程序的根工厂,或 违约 根工厂。一次 traversal 找到了一个 context 资源, view lookup 将以与在基于“纯”遍历的应用程序中调用几乎完全相同的方式进行调用。

假设没有 全球的 root factory 在此应用程序中配置。这个 违约 root factory 无法遍历;它没有用处 __getitem__ 方法。因此,我们需要将此路由配置与自定义根工厂相关联,以便创建一个有用的混合应用程序。为此,假设我们在一个名为 routes.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Resource(object):
    def __init__(self, subobjects):
       self.subobjects = subobjects

    def __getitem__(self, name):
       return self.subobjects[name]

root = Resource(
           {'a': Resource({'b': Resource({'c': Resource({})})})}
       )

def root_factory(request):
    return root

上面我们定义了一个可以遍历的(伪)资源树,以及 root_factory 可作为特定路由配置语句的一部分使用的函数:

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

这个 factory 以上是我们定义的函数。它将返回 Resource 每当此路由匹配时,将类作为根对象。的实例 Resource 类可用于树遍历,因为它们具有 __getitem__ 做一些名义上有用的事情的方法。因为遍历使用 __getitem__ 要遍历资源树的资源,对路由语句所隐含的根资源进行遍历是一件合理的事情。

注解

我们也可以用我们的 root_factory 作为 root_factory 论证 Configurator 而不是将其与路由配置中的特定路由关联。所有匹配的混合路由配置,但是 not 名字A factory 属性,将使用全局 root_factory 函数生成根对象。

当路由配置命名为 home 上面是在请求期间匹配的,生成的matchdict将基于其模式: {{foo}}/{{bar}}/*traverse . “捕获值”由 *traverse 模式中的元素将用于遍历资源树,以便从根工厂返回的根对象开始查找上下文资源。在上面的示例中, root 找到的对象将是名为 root 在里面 routes.py .

如果与模式匹配的路由的URL {{foo}}/{{bar}}/*traversehttp://example.com/one/two/a/b/c ,用于根对象的遍历路径将 a/b/c . 因此, Pyramid 将尝试穿过边缘 'a''b''c' ,从根对象开始。

在上面的示例中,这组特定的遍历步骤意味着 context 视图的资源将是 Resource 我们命名的对象 'c' 在我们的伪资源树中, view name 遍历的结果将是空字符串。如果你需要一个关于为什么假设这个结果的更新,请参见 遍历算法 .

此时,将找到合适的可调用视图,并使用 view lookup 如上所述 查看配置 ,但需要注意:为了使视图查找工作,我们需要定义一个与 view lookup 在路由匹配后调用:

1
2
3
config.add_route('home', '{foo}/{bar}/*traverse',
                 factory='mypackage.routes.root_factory')
config.add_view('mypackage.views.myview', route_name='home')

请注意,上述调用 add_view() 包括一个 route_name 争论。查看包含 route_name 参数的作用是使用路由的名称将特定的视图声明与路由关联,以指示该视图应该 仅在路由匹配时调用 .

呼叫 add_view() 可以通过 route_name 属性,它引用现有路由的值 name 争论。在上面的示例中,路由名称是 home ,引用上面定义的路由的名称。

以上 mypackage.views.myview 当满足以下条件时,将调用View Callable:

  • 名为“home”的路线匹配。
  • 这个 view name 遍历的结果是空字符串。
  • 这个 context 资源是任何对象。

当混合路由匹配时,也可以声明可调用的可选视图:

1
2
3
4
5
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')

这个 add_view 需要 mypackage.views.another_view 上面提到了不同的观点,更重要的是,不同的观点 view name . 以上 mypackage.views.another_view 满足以下条件时将调用视图:

  • 名为“home”的路线匹配。
  • 这个 view name 由遍历产生的 another .
  • 这个 context 资源是任何对象。

例如,如果URL http://example.com/one/two/a/another 提供给使用前面提到的资源树的应用程序, mypackage.views.another_view 将调用View Callable而不是 mypackage.views.myview 查看Callable,因为 view nameanother 而不是空字符串。

可以组成更复杂的匹配。所有参数 路线 配置语句和 view 在混合应用程序中支持配置语句(例如 predicate 争论)。

使用 traverse 路由定义中的参数

而不是使用 *traverse 余数标记在模式中,可以使用 traverse 论据 add_route() 方法。

当你使用 *traverse 剩余标记,当路由匹配时,遍历路径被限制为请求URL的剩余段。但是,当您使用 traverse 参数或属性,您可以更好地控制如何组成遍历路径。

这里有一个使用 traverse 调用中的模式 add_route()

1
2
config.add_route('abc', '/articles/{article}/edit',
                 traverse='/{article}')

的语法 traverse 论点与之相同 pattern .

如果,如上所述, pattern 提供的是 /articles/{{article}}/edittraverse 提供的参数为 /{{article}} ,当一个请求出现,导致路由匹配时, article 匹配值为 1 (当请求URI为 /articles/1/edit )遍历路径将生成为 /1 . 这意味着根对象 __getitem__ 将用名称调用 1 在穿越阶段。如果 1 对象存在,它将成为 context 请求。这个 遍历 第章有更多关于遍历的信息。

如果遍历路径包含模式参数中不存在的段标记名,则会发生运行时错误。这个 traverse 模式不应包含不存在于 path .

请注意 traverse 当参数附加到具有 *traverse 图案中的余数标记。

遍历将从该路由隐含的根对象(全局根或由返回的对象)开始 factory 与此路由关联)。

使全局视图匹配

默认情况下,仅查看提到 route_name 在视图查找过程中,当具有 *traverse 在它的模式匹配。您可以不使用 route_name 属性以通过添加 use_global_views 标记为路由定义。例如, myproject.views.bazbuz 如果路由名为 abc 下面是匹配的 PATH_INFO/abc/bazbuz ,即使视图配置语句没有 route_name="abc" 属性。

1
2
config.add_route('abc', '/abc/*traverse', use_global_views=True)
config.add_view('myproject.views.bazbuz', name='bazbuz')

使用 *subpath 以路线模式

有一些非常罕见的情况,当你想影响遍历 subpath 当路由匹配而没有实际执行遍历时。例如, pyramid.wsgi.wsgiapp2() 装饰师和 pyramid.static.static_view 帮助程序尝试计算 PATH_INFO 从请求的子路径 use_subpath 论证是 True ,所以能够影响这个值很有用。

什么时候? *subpath 存在于模式中,没有实际遍历路径,但遍历算法将返回 subpath 捕获值所隐含的列表 *subpath . 您将在如下所示的路由声明中看到这种模式:

1
2
3
4
5
6
from pyramid.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')

mypackage.views.www 是的实例 pyramid.static.static_view . 这有效地告诉静态助手以文件名的形式遍历子路径中的所有内容。

生成混合URL

1.5 新版功能.

这个 pyramid.request.Request.resource_url() 方法与 pyramid.request.Request.resource_path() 方法都接受可选的关键字参数,使生成包含遍历资源路径的路由前缀URL更容易: route_nameroute_kwroute_remainder_name .

具有包含 *remainder 模式(任何Stararg余数模式,如 *traverse*subpath*fred )可以用作的目标名称 request.resource_url(..., route_name=)request.resource_path(..., route_name=) .

例如,假设您在金字塔应用程序中定义了这样一个路由:

config.add_route('mysection', '/mysection*traverse')

如果要生成URL http://example.com/mysection/a/ ,您可以使用下面的咒语,假设变量 a 下面指向的资源是根目录的子目录 __name__ 属于 a

request.resource_url(a, route_name='mysection')

只能生成路径部分 /mysection/a/ 假设相同:

request.resource_path(a, route_name='mysection')

路径是虚拟主机感知的,因此如果 X-Vhm-Root 环境变量存在于请求中,并设置为 /a ,上述要求 request.resource_url 将生成 http://example.com/mysection/ 以及上述要求 request.resource_path 将生成 /mysection/ . 见 虚拟根支持 更多信息。

如果您尝试使用的路由需要填写简单的动态部分值才能成功生成URL,则可以将其作为 route_kw 参数 resource_urlresource_path . 例如,假设路由定义是这样的:

config.add_route('mysection', '/{id}/mysection*traverse')

你可以通过 route_kw 在填写 {{id}} 以上:

request.resource_url(a, route_name='mysection', route_kw={'id':'1'})

如果你通过 route_kw 但不通过 route_nameroute_kw 将被忽略。

默认情况下,此功能通过调用 route_url 在引擎盖下,并将资源路径的值传递给该函数 traverse . 如果你的路线不同 *stararg 余数名称(例如 *subpath 你可以告诉 resource_urlresource_path 用它代替 traverse 旁路 route_remainder_name . 例如,如果您有以下路由:

config.add_route('mysection', '/mysection*subpath')

你可以填写 *subpath 使用价值 resource_url 通过做:

request.resource_path(a, route_name='mysection',
                     route_remainder_name='subpath')

如果你通过 route_remainder_name 但不通过 route_nameroute_remainder_name 将被忽略。

如果你想用 resource_pathresource_urlroute_name 参数指向一个没有剩余starg的路由,不会引发错误,但生成的URL也不会包含任何剩余信息。

通常可以通过的所有其他值 resource_pathresource_url (如 queryanchorhostport ,和位置元素)按您在此配置中的预期工作。

请注意,此功能与 __resource_url__ 特征(见) 覆盖资源URL生成 )在资源对象上实现。任何 __resource_url__ 当您通过时,将忽略您的资源提供的 route_name .