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_id和employee_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()
当解析路径时,还会创建一个从模型到根的完整祖先链。可能没有为特定路径注册特定的工厂函数。在我们的当前注册表中,这些模式确实存在:departments、departments/:department_id和departments/: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_id和employee_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。
已知的转换器有unicode、str、int、`unicodelist、strlist和intlist。如果没有指定转换器,则unicode是默认转换器。可以使用register_converter注册新转换器。如果转换器引发ValueError,则无法解析路径。
0.9 (2009-11-16)
首次公开发布。
下载
项目详情
traject-0.10.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 062489968803b6403de6158be399ca02600db6c11da1437eb639bce9be5d4897 |
|
MD5 | 2fcd088044caa8e9f6397b28abbd3d19 |
|
BLAKE2b-256 | 4aee381f98027f67fc68e1947df9fbfbfefd998a9a9389aa29da4bf47858152f |