跳转到主要内容

URL分发到对象系统,结合了路由和遍历的方面。

项目描述

Traject

简介

在Web应用程序构建中,有两种主要方式将对象发布到Web:路由和遍历。两者都是一种URL分发形式:最终,根据URL中的模式调用一个函数或方法。然而,它们完成这一目标的方法却非常不同。

路由 中,将URL模式映射到被调用来生成渲染的Web页面的控制器(或视图)。URL模式也用于从URL中提取参数信息,然后可以传递这些信息。

以URL departments/10/employees/17 为例。可以存在一个URL模式,将所有 departments/*/employees/* 模式映射到特定的可调用对象。此外,路由系统还可以用来声明参数 10`17 应该从这个URL中提取并作为参数传递给控制器。然后,程序员编写控制器以使用这些信息从数据库中检索正确的模型。之后,控制器使用这些模型中的信息来构建视图的内容,例如通过使用HTML模板进行渲染。

在遍历过程中,没有显式地将URL映射到控制器或视图。相反,通过URL逐步遍历模型结构。通过类比,在Python中可以通过嵌套字典(d['a']['b']['c'])或属性(d.a.b.c)进行遍历。最后,查找可以调用的最终模型对应的视图。视图可能是模型上的特殊属性。更复杂的系统可以将视图与模型分离。

URL departments/10/employees/17 将被解析为一个视图,因为存在一个包含 department 模型对象的 departments 容器模型。反过来,从 department 模型可以遍历到 employees 容器,进而允许遍历到单个员工,例如员工17。最后,查找员工17的视图并调用。

路由通常与关系数据库结合使用,通常通过对象关系映射器将对象暴露给对象。遍历在内存对象结构或对象数据库中通常更为方便。

路由具有以下优势

  • 它是暴露没有自然嵌套的关系内容的良好方式。

  • 模式注册表为开发人员提供了对应用程序中URL模式的确切概述。

  • 该方法是熟悉的,因为它被许多框架使用。

遍历也有优势

  • 它是暴露具有任意嵌套的对象内容的良好方式。

  • 模型驱动:对象配备其视图。这允许开发人员从模型中构建应用程序,支持声明式方法。

  • 位置感知:嵌套对象结构可以很容易地实现位置感知。每个模型都知道其父级和URL中的名称。这允许为任意模型轻松构建URL。此外,还可以根据此结构声明权限。

Traject试图在单个系统中结合路由和遍历的特性。Traject

  • 看起来像路由系统,并且具有路由方法的熟悉感。

  • 对于暴露关系模型效果很好。

  • 允许开发人员明确声明URL映射。

  • 支持任意嵌套,因为URL映射可以是嵌套的,并且该系统也易于与常规遍历结合。

  • 是模型驱动的。路由是对模型,而不是对视图或控制器。

  • 是位置感知的。模型位于嵌套结构中,并且知道其父级和名称,这允许基于模型的声明安全性和模型URL的简单构建。

Traject的一些潜在缺点是

  • Traject期望其模式有一定的规律性。它不允许某些复杂的URL模式,其中多个变量是单个步骤的一部分(例如,foo/<bar_id>-<baz_id>)。每个URL段只允许一个变量。

  • Traject需要为路由中的每个阶段构建或检索模型以构建嵌套结构。这可能意味着每个请求都要进行更多的数据库查询。在实际应用中,通常由于视图逻辑通常需要结构中的父模型,因此这种情况经常得到缓解。

  • 在Traject中,每个模型实例应在URL结构中有一个且只有一个位置。这不仅允许将URL解析为模型,还允许为模型生成URL。如果您希望同一模型可以通过多个URL访问,可能会遇到一些困难。

URL模式

让我们考虑一个由斜杠分隔的一系列步骤组成的URL模式字符串

>>> pattern_str = 'foo/bar/baz'

我们可以使用parse函数将其分解为其组件步骤

>>> import traject
>>> traject.parse(pattern_str)
('foo', 'bar', 'baz')

步骤也可以是变量。变量是前面带有冒号(:)的步骤

>>> traject.parse('foo/:a/baz')
('foo', ':a', 'baz')

一个模式中允许有多个变量步骤

>>> traject.parse('foo/:a/baz/:b')
('foo', ':a', 'baz', ':b')

模式中的变量名需要是唯一的

>>> traject.parse('foo/:a/baz/:a')
Traceback (innermost last):
  ...
ParseError: URL pattern contains multiple variables with name: a

注册模式

在Traject中,URL路径的解析结果是一个模型。然后可以为此模型注册视图,以便显示该模型。视图查找的方式由Web框架本身决定。

您通过为每个URL模式注册一个工厂函数来告诉Traject为哪个路径返回哪个模型。此工厂函数应创建或检索模型对象。

工厂函数接收匹配到的每个变量的参数,无论匹配到哪个模式 - 工厂函数的签名应包括匹配到的模式中的所有变量。

让我们来看一个例子。

这是我们想要识别的URL模式

>>> pattern_str = u'departments/:department_id/employees/:employee_id'

我们可以看到这个URL模式中有两个参数:department_id`customer_id

我们现在定义一个模型,就像它在数据库中存储的那样

>>> class Employee(object):
...   def __init__(self, department_id, employee_id):
...     self.department_id = department_id
...     self.employee_id = employee_id
...   def __repr__(self):
...     return '<Employee %s %s>' % (self.department_id, self.employee_id)

我们定义这个URL模式的工厂函数,以便返回这个模型的实例。在这种情况下,参数将是department_idemployee_id

>>> def factory(department_id, employee_id):
...   return Employee(department_id, employee_id)

在这种情况下,工厂函数只是动态创建一个Employee对象。在关系数据库的上下文中,它可以根据提供的参数执行数据库查询。如果工厂返回None,这表示系统无法匹配URL:找不到对象。

为了注册此工厂函数,我们需要一个模式注册表,因此我们将创建一个

>>> patterns = traject.Patterns()

模式需要注册到特定的类或(zope.interface)接口。这是为了支持多个模式注册表,每个注册表都与一个特定的根对象相关联。在这种情况下,我们将为类Root注册模式

>>> class Root(object):
...    pass

现在我们可以注册URL模式和工厂

>>> patterns.register(Root, pattern_str, factory)

解析路径

我们现在准备解析路径。路径是URL的一部分,如foo/bar/baz。它看起来非常像模式,但所有变量都已经填充。

通过解析路径检索到的模型将被定位。最终,它们的祖先将是一个特定的根模型,所有路径都从这个根模型解析。根模型本身不是通过模式解析的:它是所有模式解析的根。

我们首先创建一个根模型

>>> root = Root()

当解析路径时,还会创建一个从模型到根的完整祖先链。可能没有为特定路径注册特定的工厂函数。在我们的当前注册表中,这些模式确实存在:departmentsdepartments/:department_iddepartments/:department_id/employees都没有注册工厂。

这些步骤将注册一个默认模型。在解析模式时,我们需要提供一个特殊的默认工厂,该工厂会在需要时生成默认模型。

让我们在这里创建一个默认工厂。工厂函数需要能够处理任意数量的关键字参数,因为可能需要提供任意数量的参数

>>> class Default(object):
...     def __init__(self, **kw):
...         pass

现在我们有了默认工厂,我们可以尝试解析一个路径

>>> obj = patterns.resolve(root, u'departments/1/employees/2', Default)
>>> obj
<Employee 1 2>
>>> obj.department_id
u'1'
>>> obj.employee_id
u'2'

另一个resolve_stack方法允许我们解析名称堆栈(其中第一个要解析的名称在堆栈顶部)

>>> l = [u'departments', u'1', u'employees', u'2']
>>> l.reverse()
>>> patterns.resolve_stack(root, l, Default)
<Employee 1 2>

转换器

在模式中可以指定转换器。转换器是一个函数,它将一个值转换成期望的值,如果无法转换则抛出 ValueError。Python 中内置的 int 就是一个转换器的例子。

在模式中,转换器通过额外的冒号后跟一个转换器标识符(在这种情况下为 int)来指定。

>>> pattern_str = u'individuals/:individual_id:int'

Traject 内置了一些转换器。

  • unicode:默认转换器。尝试将输入转换为 unicode 值。如果没有指定转换器,它将使用此转换器。

  • str:尝试将输入转换为字符串。

  • int:尝试将输入转换为整数。

  • unicodelist:尝试将输入转换为 unicode 字符串列表。输入通过 ; 字符分隔。

  • strlist:尝试将输入转换为字符串列表。输入通过 ; 字符分隔。

  • intlist:尝试将输入转换为整数列表。输入通过 ; 字符分隔。

我们现在注册模式。

>>> class Individual(object):
...   def __init__(self, individual_id):
...     self.individual_id = individual_id
...   def __repr__(self):
...     return '<Individual %s>' % self.individual_id
>>> patterns.register(Root, pattern_str, Individual)

>>> indiv = patterns.resolve(root, u'individuals/1', Default)

我们看到值确实已被转换为整数。

>>> indiv.individual_id
1

可以使用 register_converter 方法注册新的转换器。此方法接受两个参数:转换器名称和转换器函数。转换器函数应接受一个参数并将其转换为期望的值。如果转换失败,应抛出 ValueError。Python 的 int 函数是有效转换器的例子。

位置

Traject 支持位置的概念。找到模型后,模型将接收两个特殊属性

*  ``__name__``: the name we addressed this object with in the path.
  • __parent__:模型的双亲。这是一个与双亲路径(不带最后一步的路径)匹配的模型。

双亲也会有双亲,一直追溯到终极祖先,即根。

我们可以通过查看之前检索到的对象来演示祖先链。

>>> obj.__name__
u'2'
>>> isinstance(obj, Employee)
True
>>> p1 = obj.__parent__
>>> p1.__name__
u'employees'
>>> isinstance(p1, Default)
True
>>> p2 = p1.__parent__
>>> p2.__name__
u'1'
>>> isinstance(p2, Default)
True
>>> p3 = p2.__parent__
>>> p3.__name__
u'departments'
>>> isinstance(p3, Default)
True
>>> p3.__parent__ is root
True

默认对象已经为每一步创建,直到根。

消费路径

在混合的 traject/traversal 环境中,例如,在通过遍历进行视图查找时,能够根据已注册的模式解析路径可能很有用。其余步骤不再执行,并假定通过遍历系统以其他方式使用。

consume 方法将尽可能消费步骤,返回尚未消费的步骤,已消费的步骤以及它成功找到的对象。

>>> unconsumed, consumed, last_obj = patterns.consume(root,
...       'departments/1/some_view', Default)

departments/1/some_view 不能完全由模式 departments/:department_id/employees/:employee_id 消费,因为 some_view 与预期的 employees 不匹配。

我们可以看到路径的哪些部分无法消费。

>>> unconsumed
['some_view']

以及路径的哪些部分作为模式的一部分被消费。

>>> consumed
['departments', '1']

我们成功消费的最后对象代表 1

>>> isinstance(last_obj, Default)
True
>>> last_obj.__name__
'1'
>>> p1 = last_obj.__parent__
>>> p1.__name__
'departments'
>>> p1.__parent__ is root
True

consume_stack 方法使用堆栈执行相同的操作。

>>> l = ['departments', '1', 'some_view']
>>> l.reverse()
>>> unconsumed, consumed, last_obj = patterns.consume_stack(root, l, Default)
>>> unconsumed
['some_view']
>>> consumed
['departments', '1']
>>> isinstance(last_obj, Default)
True
>>> last_obj.__name__
'1'
>>> p1 = last_obj.__parent__
>>> p1.__name__
'departments'
>>> p1.__parent__ is root
True

给予模型其位置

模型在遍历后自动获得其位置。然而,还有一种情况,给对象一个位置可能很有用。例如,我们可以从一个查询中检索一个对象,然后希望为其构造一个URL,或者检查它是否有位置相关的权限。因此,Traject还提供了重建对象位置的功能。

为了做到这一点,我们需要为每个模型类注册一个特殊函数,它是工厂的逆过程。给定一个模型实例,它需要返回模式中使用的参数。因此,对于以下模式

>>> pattern_str = u'departments/:department_id/employees/:employee_id'

和给定的模型,我们需要从该模型中重建department_idemployee_id参数。

这是一个为Employee执行的函数

>>> def employee_arguments(obj):
...     return {'employee_id': obj.employee_id,
...             'department_id': obj.department_id}

在注册它时,我们还需要提供可以重建参数的类,在这种情况下,是Employee

>>> patterns.register_inverse(Root, Employee, pattern_str, employee_arguments)

现在让我们构建一些员工

>>> m = Employee(u'13', u'27')

它没有位置(没有__name____parent__

>>> m.__name__
Traceback (most recent call last):
  ...
AttributeError: ...

>>> m.__parent__
Traceback (most recent call last):
  ...
AttributeError: ...

我们现在可以使用locate方法来定位它

>>> patterns.locate(root, m, Default)

该模型现在将具有__name____parent__属性

>>> m.__name__
u'27'
>>> p1 = m.__parent__
>>> p1.__name__
u'employees'
>>> p2 = p1.__parent__
>>> p2.__name__
u'13'
>>> p3 = p2.__parent__
>>> p3.__name__
u'departments'
>>> p3.__parent__ is root
True

全局模式注册表

由于模式注册表足够聪明,可以区分根,因此在许多情况下只需要一个全局的Patterns注册表。在traject命名空间中提供了顶级函数,用于操作和使用此模式注册表。

traject.register
traject.register_inverse
traject.register_converter
traject.resolve
traject.resolve_stack
traject.consume
traject.consume_stack
traject.locate

变更

0.10.1 (2010-01-18)

  • 修复转换逻辑中的错误:当使用traject.locate时在某些情况下会中断。

0.10 (2009-11-26)

  • 添加路径段的转换功能。这使得确保路径段是某种类型(例如整数)成为可能,使用a/:id:int

    已知的转换器有unicodestrint`unicodeliststrlistintlist。如果没有指定转换器,则unicode是默认转换器。可以使用register_converter注册新转换器。如果转换器引发ValueError,则无法解析路径。

0.9 (2009-11-16)

  • 首次公开发布。

下载

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码发行版

traject-0.10.1.tar.gz (24.6 kB 查看哈希)

上传时间: 源代码

支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面