Pecan
是一个 WSGI(Web Server Gateway Interface)
对象调度 web 框架,具有架构设计精妙、响应快速,依赖较少的特点。在 OpenStack API 框架中使用较多。
安装
pip install pecan
使用
$ pecan create test_pecan
$ cd test_pecan
$ tree . | grep -v pyc
.
├── MANIFEST.in
├── config.py
├── public
│ ├── css
│ │ └── style.css
│ └── images
│ └── logo.png
├── setup.cfg
├── setup.py
└── test_pecan
├── __init__.py
├── app.py
├── controllers
│ ├── __init__.py
│ └── root.py
├── model
│ ├── __init__.py
├── templates
│ ├── error.html
│ ├── index.html
│ └── layout.html
└── tests
├── __init__.py
├── config.py
├── test_functional.py
└── test_units.py
app = {
'root': 'test_pecan.controllers.root.RootController',
'modules': ['test_pecan'],
- root:
RootController
所在的路径,API 的根 /
路径
- modules:
app.py
文件所在的包,即 setup_app
方法所在的包
- debug:是否开启调试,生产环境建议配置为 False
分发式的路由
当 RootController
继承pecan.rest.RestController
时,存在URL映射关系如下表所示:
Method |
Description |
Example Method(s) / URL(s) |
get_one |
Display one record. |
GET /books/1 |
get_all |
Display all records in a resource. |
GET /books/ |
get |
A combo of get_one and get_all. |
GET /books/ 或 GET /books/1 |
new |
Display a page to create a new resource. |
GET /books/new |
edit |
Display a page to edit an existing resource. |
GET /books/1/edit |
post |
Create a new record. |
POST /books/ |
put |
Update an existing record. |
POST /books/1?_method=put 或 PUT /books/1 |
get_delete |
Display a delete confirmation page. |
GET /books/1/delete |
delete |
Delete an existing record. |
POST /books/1?_method=delete 或 DELETE /books/1 |
示例
示例1
from pecan import expose, rest
class RootController(rest.RestController):
_custom_actions = {
'test': ['GET', 'POST'],
}
@expose()
def get_one(self, arg):
return 1
@expose()
def test(self):
# header: pecan.request.headers.get('Content-Type')
# querys: pecan.request.GET.items():
if pecan.request.method == 'POST':
return 'This is RootController POST test.'
elif pecan.request.method == 'GET':
return 'This is RootController GET test.'
其中:
_custom_actions
指定 test 方法接受的请求类型
@expose()
指定返回值
在这个例子中,可以使用curl去测试:
$ curl -X GET http://127.0.0.1:8000/test
This is RootController GET test.
$ curl -X POST http://127.0.0.1:8000/test
This is RootController POST test.
示例2
from pecan import expose
class RootController(object):
@expose()
def index(self, arg):
return arg
@expose()
def args(self, *args):
return ','.join(args)
@expose()
def kwargs(self, **kwargs):
return str(kwargs)
1. query string to arguments
$ curl http://127.0.0.1:8000/?arg=foo
foo
$ curl http://127.0.0.1:8000/kwargs?a=1&b=2&c=3
{u'a': u'1', u'c': u'3', u'b': u'2'}
2. remaining path to arguments
$ curl http://127.0.0.1:8000/args/one/two/three
one,two,three
3. POST body to arguments
$ curl -X POST "http://127.0.0.1:8000/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo"
foo
$ curl -X POST "http://127.0.0.1:8000/kwargs" -H "Content-Type: application/x-www-form-urlencoded" -d "name=foo;age=18"
WSME
WSME(Web Service Made Easy)
是用于实现 REST
服务的 typing
库,一般和Pecan结合使用,如 OpenStack 的很多项目都使用了 Pecan + WSME 的组合来实现 API。
WSME
模块用来规范API的请求和响应值:
Type |
Json type |
str |
String |
unicode |
String |
int |
Number |
float |
Number |
bool |
Boolean |
Decimal |
String |
date |
String (YYYY-MM-DD) |
time |
String (hh:mm:ss) |
datetime |
String (YYYY-MM-DDThh:mm:ss) |
Arrays |
Array |
None |
null |
Complex types |
Object |
例子如下:
from wsmeext.pecan import wsexpose
from pecan import rest
class RootController(rest.RestController):
@wsexpose(int, int)
def get_one(self, arg):
return 1
其中:
@signature
: 这个装饰器用来描述一个函数的输入和输出
@wsexpose(int, int)
中第一个int
表示返回值必须为int,第二个int
表示请求参数必须为int。包@wsexpose
含 @signature
的功能,效果就像Pecan的expose装饰器
在这个例子中,可以使用curl去测试:
$ curl -X GET http://127.0.0.1:8000/1
1
示例说明
以学生类为例:
{"students": [{"name": "foo", "age": 17}, {"name": "bar", "age": 18}]}
定义 Student
类和 Students
类型:
from wsme import types as wtypes
class Student(wtypes.Base):
name = wtypes.text
age = int
class Students(wtypes.Base):
students = [Student]
Post/Put Body 约束
class RootController(rest.RestController):
# 检查参数必须为 Student 对象
@wsexpose(None, body=Student)
def post(self, stu):
print(f'name: {stu.name}, age: {stu.age}')
curl -X POST http://127.0.0.1:8000 -H "Content-Type: application/json" -d '{"name": "foo2", "age": 18}'
如果 post body 不是 Student
对象,将会报错。
返回值约束
from wsmeext.pecan import wsexpose
from pecan import rest
class RootController(rest.RestController):
# 返回值为Student类对象,int 为传入的参数
@wsexpose(Student, int)
def get_one(self, id):
if id == 1:
stu_info = {
'name': 'bar',
'age': 18
}
# return Student(name='bar', age=18)
return Student(**stu_info)
else:
raise wsme.exc.ClientSideError('something is wrong!', status_code=403)
@wsexpose(Students)
def get_all(self):
stu_info_list = [
{
'name': 'foo',
'age': 17
},
{
'name': 'bar',
'age': 18
}
]
# return Students(students=[Student(name='foo', age=17), Student(name='bar', age=18)])
return Students(students=[Student(**stu_info) for stu_info in stu_info_list])
$ curl http://127.0.0.1:8081/1
{"name": "bar", "age": 18}
$ curl http://127.0.0.1:8081
{"students": [{"name": "foo", "age": 17}, {"name": "bar", "age": 18}]}
其他
如果觉得 wsexpose
约束太严格,可以使用如下通用方法
import pecan
from pecan import rest
class TestController(rest.RestController):
@pecan.expose('json')
def get(self):
return {"version": "1.0.0"}
@pecan.expose('json', int)
def put(self, foo):
#return "foo" # positive control
return pecan.request.body
@pecan.expose('json', int)
def post(self, foo):
#return "foo" # positive control
return pecan.request.body
@pecan.expose('json', int)
def patch(self, foo):
#return "foo" # positive control
return pecan.request.body
$ curl -X POST http://127.0.0.1:8080/test/1 -d '{}'
"{}"
$ curl -X PUT http://127.0.0.1:8080/test/1 -d '{}'
"{}"
$ curl -X PATCH http://127.0.0.1:8080/test/1 -d '{}'
"{}"
status_code
以下示例返回值为 201
from wsmeext.pecan import wsexpose
from pecan import rest
class RootController(rest.RestController):
@wsexpose(int, int, status_code=201)
def get_one(self, arg):
return 1
自定义 expose
如果用户没有指定返回值类型,默认为 json
格式:
import wsmeext.pecan as wsme_pecan
def expose(*args, **kwargs):
if 'rest_content_types' not in kwargs:
kwargs['rest_content_types'] = ('json',)
return wsme_pecan.wsexpose(*args, **kwargs)
分布式路由
通常
from pecan import expose, rest
class v1Controller(rest.RestController):
@expose()
def get(self):
return 'This is v1Controller GET.'
class RootController(rest.RestController):
v1 = v1Controller()
$ curl -X GET http://127.0.0.1:8000/v1
This is v1Controller GET.
_lookup
from pecan import expose, abort
from somelib import get_student_by_name
class StudentController(object):
def __init__(self, student):
self.student = student
@expose()
def name(self):
return self.student.name
class RootController(object):
@expose()
def _lookup(self, primary_key, *remainder):
student = get_student_by_primary_key(primary_key)
if student:
return StudentController(student), remainder
else:
abort(404)
# 获取 id == 8 的名称
$ curl -X GET http://127.0.0.1:8000/8/name
Templating
pecan.expose
支持的渲染模板:
- Mako
- Genshi
- Kajiki
- Jinja2
- JSON