Pecan 是轻量级的 Python web 开发框架,它用于填补 Python web 框架世界中的一个空白:即提供对象分发(object-dispatch)风格的路由。Pecan 并不打算成为一个全栈框架,因此并不包含一些开箱即用的工具:例如对 sessions、databases 的支持,相反 Pecan 聚焦于 HTTP 本身。
尽管 Pecan 非常轻量,但是仍然为构建基于 HTTP 的应用程序提供大量扩展支持:
- 采用对象分发式的路由风格
- 对 REST 风格控制器的完全支持
- 可扩展的安全框架
- 可扩展的模板语言支持
- 可扩展的 JSON 输出
- 简易的基于 Python 的配置
安装 Pecan
首先创建一个虚拟环境:
1 | $ virtualenv pecan-env |
之后使用 pip
安装 Pecan
:
1 | pip install pecan |
牛刀小试
接下来将创建一个 Pecan 应用程序,这里直接使用 pecan create
命令,它会基于模板创建一个 Pecan 应用程序:
1 | pecan create hello_pecan |
之后进入 hello_pecan
程序目录,使用开发模式安装到系统,这样对源码的修改可以立即生效:
1 | cd hello_pecan |
Pecan 基于模板创建的程序目录中包含如下主要文件:
- public 目录:存放所有的静态文件。Pecan 自带一个简答的文件服务器,因此开发模式下可以直接访问这些文件
- config.py:包含了运行 Pecan 应用程序所需要的基本配置。为了使用方便,Pecan 使用纯 Python 文件作为配置文件
Pecan 应用程序的结构也遵循 MVC 模式,因此包含如下目录:
- hello_pecan/controllers:存放所有的 controller 文件
- hello_pecan/templates:存放所有的 template 文件
- hello_pecan/templates:存放所有的 model 文件
还包含一个测试目录:
- hello_pecan/tests:用于存放单元测试和集成测试文件:
最后再分析 hello_pecan/app.py 文件,它用于控制 Pecan 程序如何创建。它包含一个 setup_app() 函数用于返回一个 WSGI 应用程序。
接下来使用 pecan serve
命令启动一个开放服务器,并运行该应用程序:
1 | $ pecan serve config.py |
应用程序入口点
RootController 是应用程序的入口点。我们测试程序的 RootController 位于 hello_pecan/controllers/root.py
文件中:
1 | from pecan import expose, redirect |
简单分析一下这段代码:
- index 方法通过 expose() 装饰器被标记为可公开访问,所以访问应用程序的 root(即
/
)的 HTTP GET 请求都会被路由到该方法 - index 方法返回一个字典,该字典将被用于渲染特定的模板,这里即为
index.html
,从而返回一个 HTML。这也是将数据从 controller 传给 template 的主要机制 index_post()
被@index.when(method='POST')
装饰器修饰,因此所有访问应用程序 root 的 HTTP POST 请求都会被路由到该方法。POST 请求的参数也会被传递给 index_post 方法error()
方法允许对 HTTP 错误显示自定义页面
Controller 和 路由
Pecan 使用对象分发
(object-dispatch)的路由策略来将 HTTP 请求映射到某一个 controller,然后调用其相应的方法。对象分发会把访问 URI 分割为一系列的组件,然后根据路径访问对象,首先是 root controller。可以把应用程序所有的 controller 想象为 objects 树,对象树的某个分支就直接映射为相应的 URL 路径。
举个例子:
1 | from pecan import expose |
如果某一个请求访问路径 /catalog/books/bestsellers
,那么 Pecan 会把该请求分割为 catelog
、books
、bestsellers
、然后会在 RootController
下寻找 catelog
对象,然后使用 catelog
对象,寻找 books
对象,之后再在 books 对象中寻找 bestsellers
。如果 URI 的最后部分是一个 controller 对象,则调用该 controller 对象的 index
方法。
例如访问如下路径:
1 | └── / |
最终会调用如下 controller 的方法:
1 | └── RootController.index |
导出 controller
通过 expose
装饰器可以告诉 Pecan 类中的哪些方法是公共可见的,如果类中的方法没有使用 expose()
装饰,Pecan 不会将该请求路由到该方法上。expose()
可以有多种用途:
- 如果没有传递任何参数,那么 controller 方法返回的内容将以字符串的形式作为 HTML 响应体
- 更常见的场景是在
expose()
方法中将需要渲染的模板作为参数,然后controller
方法返回一个字典,作为渲染的输入(namespace) - expose() 方法可以叠加,这样可以根据请求(例如根据请求头中的
Accept
字段) 返回不同的样式内容。
指定显式的路径分段
有时,你可能会碰到这样一个问题:URI 中的路径分段和 Pecan 控制器中的方法名称不一致(例如受限于 Python 的语法)。举个例子:访问的路径中包含 -
,那么在 Python 中如下方法是非法的:
1 | class RootController(object): |
为了解决该问题,可以在 expose
装饰器指定显式的路径分段:
1 | class RootController(object): |
此时可以顺利访问 /some-path
,但是访问 /some-path
则会失败。
还有一种方式是直接在 Pecan 中使用 route
方法,如下所示:
1 | class RootController(object): |
基于请求方法进行路由
expose()
方法的 generic
参数使得我们可以基于请求方法来重载 URL。例如下面的例子中,虽然是同一个 URL,但是可以被两个不同的方法访问,每个请求方法路由到对应的 controller method 上。这里使用到了 generic controller
:
1 | class RootController(object): |
使用 _lookup
路由到 Subcontrollers
有时标准的对象分发机制不足以将某个 URL 映射到对应的 controller。 Pecan 提供一些方法可以对路由机制提供更多控制,从而为 URL 路由提供更大的灵活性。
_lookup()
方法提供了一种方式:只处理 URL 中的一部分,然后返回一个新的控制器,用来处理 URL 的剩余部分。_lookup()
方法可以接受一个或多个参数,用于表示 URL 中要处理的分段。另外它也包含一个可变长位置参数,用于表示不处理的 URL 分段。而且,所有不处理的 URL 分段也需要被返回。
除了使用 _lookup
来动态创建 controllers
,_lookup()
也是 Pecan 最后尝试调用的函数(当 controller 的所有方法都不匹配 URL 且没有 _default()
方法。
1 | from pecan import expose, abort |
这样访问 /8/name
的 HTTP GET 请求会返回指定 Student 的 name,该 Student 的 primary_key
== 8
_default()
方法
在通过标准的对象分发对 URL 进行路由时,_default()
方法是最后尝试调用的方法:
1 | from pecan import expose |
在该例子中,访问 /spanish
,会导致请求路由到 RootController._default()
方法。
_route
自定义路由
_route()
方法允许控制器完全覆盖 Pecan 的路由机制,Pecan 使用 _route()
方法来实现它的 RestController。如果你想在 Pecan 之上设计另一套路由系统,可以定义一个包含自定义 _route()
方法的基控制器类。
和 Request 和 Response 对象进行交互
对于每一个 HTTP 请求,Pecan 都会维护一个对 request
/response
对象的 thread-local
引用:pecan.request
和 pecan.response
。
1 |
|
可以通过 abort() 函数来抛出一个 WSGIHTTPException
类型的异常,Pecan 会使用该异常来渲染 HTTP 错误的默认响应 body。
WebOp 提供的请求和响应实现已经足够强大了,但是有时我们还是对 request
和 response
进行扩展。如果想实现这一点,可以继承 pecan.Request
和 pecan.Response
创建自定义对应类型:
1 | class MyRequest(pecan.Request): |
然后修改程序配置:
1 | from myproject import MyRequest, MyResponse |
控制器参数
HTTP GET 和 POST 请求中的未被路由过程处理的的变量都会被传递给控制器的方法,并作为其参数。取决于方法的签名,这些参数可以进行显示映射:
1 | from pecan import expose |
1 | $ curl http://localhost:8080/?arg=foo |
HTTP POST 请求 body 中的变量也可以达到同样的效果:
1 | from pecan import expose |
1 | $ curl -X POST "http://localhost:8080/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo" |
Pecan 中的模板
Pecan 支持各种模板引擎,而且也很容易对其进行扩展以添加新的模板引擎。当前,Pecan 支持 Mako
、Genshi
、Kajiki
、Jinja2
、Json
的模板引擎。默认的模板引擎是 mako,可以在应用程序中通过 default_renderer
修改模板引擎。
通过 expose() 装饰器的 template
参数,可以指定控制器方法所需要使用的模板文件:
1 | class MyController(object): |
expose() 方法会使用默认的模板引擎来对模板文件进行渲染,除非模板文件中显式指定了模板引擎:
1 |
|
override_template() 允许你覆盖控制器方法中所要使用的模板文件。而render() 允许你使用 Pecan 模板框架手动进行渲染:
1 |
|
为了自定义渲染器,可以创建一个自定义类,其实现了渲染器协议:
1 | class MyRenderer(object): |
使用 Generic 控制器编写 RESTful web 服务
通过 Pecan 的 generic 控制器,简化了 RESTful web 服务的编写,因为 通用控制器
允许你基于请求方法来重载 URL。下面是一个例子程序:
1 | from pecan import abort, expose |