Flask-RESTful(转载)

Flask-RESTful 是一个 Flask 扩展,它添加了快速构建 REST APIs 的支持。它当然也是一个能够跟你现有的ORM/库协同工作的轻量级的扩展。Flask-RESTful 鼓励以最小设置的最佳实践。如果你熟悉 Flask 的话,Flask-RESTful 应该很容易上手。

用户指南

这部分文档将向你展示如何在 Flask 中使用 Flask-RESTful。

API 参考

如果你正在查询特定函数,类或者方法的信息,这部分文档就是为你准备的。

其它注意事项

关于这个项目的法律信息请参阅 Flask 的 license

安装

使用 pip 安装 Flask-RESTful:

pip install flask-restful

开发的版本可以从 GitHub 上的页面 下载

git clone https://github.com/twilio/flask-restful.git
cd flask-restful
python setup.py develop

Flask-RESTful 有如下的依赖包(如果你使用 pip,依赖包会自动地安装):

  • Flask 版本 0.8 或者更高

Flask-RESTful 要求 Python 版本为 2.6, 2.7, 或者 3.3。

快速入门

是时候编写你第一个 REST API。本指南假设你对 Flask 有一定的认识,并且已经安装了 Flask 和 Flask-RESTful。如果还没有安装的话,可以依照 安装 章节的步骤安装。

一个最小的 API

一个最小的 Flask-RESTful API 像这样:

from flask import Flask
#from flask.ext import restful
import flask.restful
 
app = Flask(__name__)
api = flask.restful.Api(app)
 
class HelloWorld(flask.restful.Resource):
    def get(self):
        return {'hello': 'world'}
 
api.add_resource(HelloWorld, '/')
 
if __name__ == '__main__':
app.run(debug=True)
 
#注:若是使用外部ip访问的话需要这样配置:
if __name__ == '__main__':
app.run(host=”0.0.0.0”,debug=True)

把上述代码保存为 api.py 并且在你的 Python 解释器中运行它。需要注意地是我们已经启用了 Flask 调试 模式,这种模式提供了代码的重载以及更好的错误信息。调试模式绝不能在生产环境下使用。

$ python api.py
 * Running on http://127.0.0.1:5000/

现在打开一个新的命令行窗口使用 curl 测试你的 API:

# 简单的访问可以直接使用浏览器操作

$ curl http://127.0.0.1:5000/
{"hello": "world"}

资源丰富的路由(Resourceful Routing)

Flask-RESTful 提供的最主要的基础就是资源(resources)。资源(Resources)是构建在 Flask 可拔插视图 之上,只要在你的资源(resource)上定义方法就能够容易地访问多个 HTTP 方法。一个待办事项应用程序的基本的 CRUD 资源看起来像这样:

from flask import Flask, request
from flask.restful import Resource, Api
 
app = Flask(__name__)
api = Api(app)
 
todos = {}
 
class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}
 
    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}
 
api.add_resource(TodoSimple, '/<string:todo_id>')
 
if __name__ == '__main__':
    app.run(debug=True)

你可以尝试这样:

$ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo1
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT
{"todo2": "Change my brakepads"}
$ curl http://localhost:5000/todo2
{"todo2": "Change my brakepads"}

或者如果你安装了 requests 库的话,可以从 python shell 中运行:

>>> from requests import put, get
>>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json()
{u'todo1': u'Remember the milk'}
>>> get('http://localhost:5000/todo1').json()
{u'todo1': u'Remember the milk'}
>>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json()
{u'todo2': u'Change my brakepads'}
>>> get('http://localhost:5000/todo2').json()
{u'todo2': u'Change my brakepads'}

Flask-RESTful 支持视图方法多种类型的返回值。同 Flask 一样,你可以返回任一迭代器,它将会被转换成一个包含原始 Flask 响应对象的响应。Flask-RESTful 也支持使用多个返回值来设置响应代码和响应头,如下所示:

class Todo1(Resource):
    def get(self):
        # Default to 200 OK
        return {'task': 'Hello world'}
 
class Todo2(Resource):
    def get(self):
        # Set the response code to 201
        return {'task': 'Hello world'}, 201
 
class Todo3(Resource):
    def get(self):
        # Set the response code to 201 and return custom headers
        return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}
注:Etag出现在浏览器的响应头
 
 

端点(Endpoints)

很多时候在一个 API 中,你的资源可以通过多个 URLs 访问。你可以把多个 URLs 传给 Api 对象的 Api.add_resource() 方法。每一个 URL 都能访问到你的 Resource

如下访问/和访问/hello效果一样:

api.add_resource(HelloWorld, '/', '/hello')
 
 

你也可以为你的资源方法指定 endpoint 参数。endpoint不设置的话默认是对应的视图函数,”参考数据格式化一栏”

api.add_resource(Todo,'/todo/<int:todo_id>', endpoint='todo_ep')

参数解析

尽管 Flask 能够简单地访问请求数据(比如查询字符串或者 POST 表单编码的数据),验证表单数据仍然很痛苦。Flask-RESTful 内置了支持验证请求数据,它使用了一个类似 argparse 的库。

from flask.ext.restful import reqparse
 
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()

需要注意地是与 argparse 模块不同,reqparse.RequestParser.parse_args() 返回一个 Python 字典而不是一个自定义的数据结构。

使用 reqparse 模块同样可以自由地提供聪明的错误信息。如果参数没有通过验证,Flask-RESTful 将会以一个 400 错误请求以及高亮的错误信息回应。

$ curl -d 'rate=foo' http://127.0.0.1:5000/
{'status': 400, 'message': 'foo cannot be converted to int'}

inputs 模块提供了许多的常见的转换函数,比如 inputs.date()inputs.url()

使用 strict=True 调用 parse_args 能够确保当请求包含你的解析器中未定义的参数的时候会抛出一个异常。

args = parser.parse_args(strict=True)

数据格式化

默认情况下,在你的返回迭代中所有字段将会原样呈现。尽管当你刚刚处理 Python 数据结构的时候,觉得这是一个伟大的工作,但是当实际处理它们的时候,会觉得十分沮丧和枯燥。为了解决这个问题,Flask-RESTful 提供了 fields 模块和 marshal_with() 装饰器。类似 Django ORM 和 WTForm,你可以使用 fields 模块来在你的响应中格式化结构。

from collections import OrderedDict
from flask.ext.restful import fields, marshal_with
 
resource_fields = {
    'task':   fields.String,
    'uri':    fields.Url('todo_ep')
}
 
class TodoDao(object):
    def __init__(self, todo_id, task):
        self.todo_id = todo_id
        self.task = task
 
        # This field will not be sent in the response
        self.status = 'active'
 
class Todo(Resource):
    @marshal_with(resource_fields)
    def get(self, **kwargs):
        return TodoDao(todo_id='my_todo', task='Remember the milk')

上面的例子接受一个 python 对象并准备将其序列化。marshal_with() 装饰器将会应用到由 resource_fields 描述的转换。从对象中提取的唯一字段是 task。fields.Url 域是一个特殊的域,它接受端点(endpoint)名称作为参数并且在响应中为该端点生成一个 URL。许多你需要的字段类型都已经包含在内。请参阅 fields 指南获取一个完整的列表。

 

完整的例子

在 api.py 中保存这个例子

#!/usr/bin/env python
#-*- coding: utf-8 -*-
 
from flask import Flask
from flask_restful import Resource,Api,fields,abort,reqparse
 
app = Flask(__name__)
api = Api(app)
 
TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}
 
def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        abort(404,message="Todo {} doesn't exist".format(todo_id) )
 
parser = reqparse.RequestParser()
parser.add_argument('task',type=str)
 
# Todo
#   show a single todo item and lets you delete them
 
class Todo(Resource):
 
    def get(self,todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]
 
    def delete(self,todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '',204
 
    def put(self,todo_id):
        args = parser.parse_args()
        task = {'task':args['task']}
        TODOS[todo_id] = task
        return task,201
 
# TodoList
#   shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
    def get(self):
        return TODOS
 
    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo %i' % todo_id
        TODOS[todo_id] = {'task':args['task']}
        return TODOS[todo_id],201
##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList,'/todos')
api.add_resource(Todo,'/todos/<todo_id>')
 
if __name__ == '__main__':
    app.run(host="0.0.0.0",debug=True)

用法示例

$ python api.py
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader

获取列表

$ curl http://localhost:5000/todos
{"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}}

获取一个单独的任务

$ curl http://localhost:5000/todos/todo3
{"task": "profit!"}

删除一个任务

$ curl http://localhost:5000/todos/todo2 -X DELETE -v
 
> DELETE /todos/todo2 HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
* HTTP 1.0, assume close after body
< HTTP/1.0 204 NO CONTENT
< Content-Type: application/json
< Content-Length: 0
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:10:32 GMT

增加一个新的任务,注意格式要求,是“=”而不是{“”:“”}

$ curl http://localhost:5000/todos -d "task=something new" -X POST -v
 
> POST /todos HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
> Content-Length: 18
> Content-Type: application/x-www-form-urlencoded
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 25
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:12:58 GMT
* Closing connection #0
{"task": "something new"}

更新一个任务

$ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v
 
> PUT /todos/todo3 HTTP/1.1
> Host: localhost:5000
> Accept: */*
> Content-Length: 20
> Content-Type: application/x-www-form-urlencoded
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 27
< Server: Werkzeug/0.8.3 Python/2.7.3
< Date: Mon, 01 Oct 2012 22:13:00 GMT
* Closing connection #0
{"task": "something different"}

请求解析

Flask-RESTful 的请求解析接口是模仿 argparse 接口。它设计成提供简单并且统一的访问 Flask 中 flask.request 对象里的任何变量的入口。

基本参数

这里是请求解析一个简单的例子。它寻找在 flask.Request.values 字典里的两个参数。一个类型为 int,另一个的类型是 str

from flask.restful import reqparse
 
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name', type=str)
args = parser.parse_args()

如果你指定了 help 参数的值,在解析的时候当类型错误被触发的时候,它将会被作为错误信息给呈现出来。如果你没有指定 help 信息的话,默认行为是返回类型错误本身的信息。

 

 

默认下,arguments 不是 必须的。另外,在请求中提供的参数不属于 RequestParser 的一部分的话将会被忽略。

另请注意:在请求解析中声明的参数如果没有在请求本身设置的话将默认为 None。

关于type的几点说明:这个是指输出字段的类型,不是输入字段的类型

type=float,type=str, type=int

 

代码示例:以下示例也都是基于下述代码为示例

# 主要是关于示例代码中黄色部分的内容,请求方法为put:(请求其他方法爱也可以,不过需要添加上args = parser.parse_args())

# curl -X PUT -d "task=123"  http://127.0.0.1:5000/todos/todo3

#!/usr/bin/env python

#-*- coding: utf-8 -*-

 

from flask import Flask

from flask_restful import Resource,Api,abort,reqparse

 

app = Flask(__name__)

api = Api(app)

 

TODOS = {

    'todo1': {'task': 'build an API'},

    'todo2': {'task': '?????'},

    'todo3': {'task': 'profit!'},

}

 

 

def abort_if_todo_doesnt_exist(todo_id):

    if todo_id not in TODOS:

        abort(404,message="Todo {} doesn't exist".format(todo_id) )

 

parser = reqparse.RequestParser()

parser.add_argument('task',type=int,help="value error")

 

# Todo

#   show a single todo item and lets you delete them

 

class Todo(Resource):

 

    def get(self,todo_id):

        abort_if_todo_doesnt_exist(todo_id)

        return TODOS[todo_id]

 

    def delete(self,todo_id):

        abort_if_todo_doesnt_exist(todo_id)

        del TODOS[todo_id]

        return '',204

 

    def put(self,todo_id):

        args = parser.parse_args()

        task = {'task':args['task']}

        TODOS[todo_id] = task

        return task,201

 

# TodoList

#   shows a list of all todos, and lets you POST to add new tasks

class TodoList(Resource):

    def get(self):

        return TODOS

 

    def post(self):

        args = parser.parse_args()

        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1

        todo_id = 'todo %i' % todo_id

        TODOS[todo_id] = {'task':args['task']}

        return TODOS[todo_id],201

 

##

## Actually setup the Api resource routing here

##

api.add_resource(TodoList,'/todos')

api.add_resource(Todo,'/todos/<todo_id>')

 

if __name__ == '__main__':

    app.run(host="0.0.0.0",debug=True)

必需的参数

要求一个值传递的参数,只需要添加 required=True 来调用 add_argument()。

parser.add_argument('name', type=str, required=True,
help="Name cannot be blank!")
 
 

多个值&列表

如果你要接受一个键有多个值的话,你可以传入 action='append'

parser.add_argument('name', type=str, action='append')

这将让你做出这样的查询

curl http://api.example.com -d "Name=bob" -d "Name=sue" -d "Name=joe"

你的参数将会像这样

args = parser.parse_args()
args['name']    # ['bob', 'sue', 'joe']
 
 

其它目标(Destinations)

如果由于某种原因,你想要以不同的名称存储你的参数一旦它被解析的时候,你可以使用 dest kwarg。一旦设置这个,其他地方都需要修改,具体看截图

parser.add_argument('name', type=str, dest='public_name')
 
args = parser.parse_args()
args['public_name']
 
 

参数位置

默认下,RequestParser 试着从 flask.Request.values,以及 flask.Request.json 解析值。

在 add_argument() 中使用 location 参数可以指定解析参数的位置。flask.Request 中任何变量都能被使用。例如:

# Look only in the POST body
parser.add_argument('name', type=int, location='form')
 
# Look only in the querystring
parser.add_argument('PageSize', type=int, location='args')
 
# From the request headers
parser.add_argument('User-Agent', type=str, location='headers')
 
# From http cookies
parser.add_argument('session_id', type=str, location='cookies')
 
# From file uploads
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')
注:若在指定位置没有解析出该参数,则返回None
 
 

多个位置

通过传入一个列表到 location 中可以指定 多个 参数位置:(具体啥意思没看懂,不知道咋实践操作)

parser.add_argument('text', location=['headers', 'values'])

列表中最后一个优先出现在结果集中。(例如:location=[‘headers’, ‘values’],解析后 ‘values’ 的结果会在 ‘headers’ 前面)

继承解析

往往你会为你编写的每个资源编写不同的解析器。这样做的问题就是如果解析器具有共同的参数。不是重写,你可以编写一个包含所有共享参数的父解析器接着使用 copy() 扩充它。你也可以使用 replace_argument() 覆盖父级的任何参数,或者使用 remove_argument() 完全删除参数。 例如:

from flask.ext.restful import RequestParser
 
parser = RequestParser()
parser.add_argument('foo', type=int)
 
parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
 
# parser_copy has both 'foo' and 'bar'
 
parser_copy.replace_argument('foo', type=str, required=True, location='json')
# 'foo' is now a required str located in json, not an int as defined
#  by original parser
 
parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument

输出字段

Flask-RESTful 提供了一个简单的方式来控制在你的响应中实际呈现什么数据。使用 fields 模块,你可以使用在你的资源里的任意对象(ORM 模型、定制的类等等)并且 fields 让你格式化和过滤响应,因此您不必担心暴露内部数据结构。

当查询你的代码的时候,哪些数据会被呈现以及它们如何被格式化是很清楚的。

基本用法

你可以定义一个字典或者 fields 的 OrderedDict 类型,OrderedDict 类型是指键名是要呈现的对象的属性或键的名称,键值是一个类,该类格式化和返回的该字段的值。这个例子有三个字段,两个是字符串(Strings)以及一个是日期时间(DateTime),格式为 RFC 822 日期字符串(同样也支持 ISO 8601)

from flask.ext.restful import Resource, fields, marshal_with
 
resource_fields = {
    'name': fields.String,
    'address': fields.String,
    'date_updated': fields.DateTime(dt_format='rfc822'),
}
 
class Todo(Resource):
    @marshal_with(resource_fields, envelope='resource')
    def get(self, **kwargs):
        return db_get_todo()  # Some function that queries the db

这个例子假设你有一个自定义的数据库对象(todo),它具有属性:name, address, 以及 date_updated。该对象上任何其它的属性可以被认为是私有的不会在输出中呈现出来。一个可选的 envelope 关键字参数被指定为封装结果输出。

装饰器 marshal_with 是真正接受你的数据对象并且过滤字段。marshal_with 能够在单个对象,字典,或者列表对象上工作。

注意:marshal_with 是一个很便捷的装饰器,在功能上等效于如下的 return marshal(db_get_todo(), resource_fields), 200。这个明确的表达式能用于返回 200 以及其它的 HTTP 状态码作为成功响应(错误响应见 abort)。

重命名属性

很多时候你面向公众的字段名称是不同于内部的属性名。使用 attribute 可以配置这种映射。

fields = {
    'name': fields.String(attribute='private_name'),
    'address': fields.String,
}

lambda 也能在 attribute 中使用

fields = {
    'name': fields.String(attribute=lambda x: x._private_name),
    'address': fields.String,
}

默认值

如果由于某种原因你的数据对象中并没有你定义的字段列表中的属性,你可以指定一个默认值而不是返回 None。

fields = {
    'name': fields.String(default='Anonymous User'),
    'address': fields.String,
}

自定义字段&多个值

有时候你有你自己定义格式的需求。你可以继承 fields.Raw 类并且实现格式化函数。当一个属性存储多条信息的时候是特别有用的。例如,一个位域(bit-field)各位代表不同的值。你可以使用 fields 复用一个单一的属性到多个输出值(一个属性在不同情况下输出不同的结果)。

这个例子假设在 flags 属性的第一位标志着一个“正常”或者“迫切”项,第二位标志着“读”与“未读”。这些项可能很容易存储在一个位字段,但是可读性不高。转换它们使得具有良好的可读性是很容易的。

class UrgentItem(fields.Raw):
    def format(self, value):
        return "Urgent" if value & 0x01 else "Normal"
 
class UnreadItem(fields.Raw):
    def format(self, value):
        return "Unread" if value & 0x02 else "Read"
 
fields = {
    'name': fields.String,
    'priority': UrgentItem(attribute='flags'),
    'status': UnreadItem(attribute='flags'),
}

Url & 其它具体字段

Flask-RESTful 包含一个特别的字段,fields.Url,即为所请求的资源合成一个 uri。这也是一个好示例,它展示了如何添加并不真正在你的数据对象中存在的数据到你的响应中。

class RandomNumber(fields.Raw):
    def output(self, key, obj):
        return random.random()
 
fields = {
    'name': fields.String,
    # todo_resource is the endpoint name when you called api.add_resource()
    'uri': fields.Url('todo_resource'),
    'random': RandomNumber,
}

默认情况下,fields.Url 返回一个相对的 uri。为了生成包含协议(scheme),主机名以及端口的绝对 uri,需要在字段声明的时候传入 absolute=True。传入 scheme 关键字参数可以覆盖默认的协议(scheme):

fields = {
    'uri': fields.Url('todo_resource', absolute=True)
    'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}

复杂结构

你可以有一个扁平的结构,marshal_with 将会把它转变为一个嵌套结构

>>> from flask.ext.restful import fields, marshal
>>> import json
>>> 
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['zip'] = fields.String
>>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'

注意:address 字段并不真正地存在于数据对象中,但是任何一个子字段(sub-fields)可以直接地访问对象的属性,就像没有嵌套一样。

列表字段

你也可以把字段解组(unmarshal)成列表

>>> from flask.ext.restful import fields, marshal
>>> import json
>>> 
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'

高级:嵌套字段

尽管使用字典套入字段能够使得一个扁平的数据对象变成一个嵌套的响应,你可以使用 Nested 解组(unmarshal)嵌套数据结构并且合适地呈现它们。

>>> from flask.ext.restful import fields, marshal
>>> import json
>>> 
>>> address_fields = {}
>>> address_fields['line 1'] = fields.String(attribute='addr1')
>>> address_fields['line 2'] = fields.String(attribute='addr2')
>>> address_fields['city'] = fields.String(attribute='city')
>>> address_fields['state'] = fields.String(attribute='state')
>>> address_fields['zip'] = fields.String(attribute='zip')
>>> 
>>> resource_fields = {}
>>> resource_fields['name'] = fields.String
>>> resource_fields['billing_address'] = fields.Nested(address_fields)
>>> resource_fields['shipping_address'] = fields.Nested(address_fields)
>>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2}
>>> 
>>> json.dumps(marshal_with(data, resource_fields))
'{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'

此示例使用两个嵌套字段。Nested 构造函数把字段的字典作为子字段(sub-fields)来呈现。使用 Nested 和之前例子中的嵌套字典之间的重要区别就是属性的上下文。在本例中 “billing_address” 是一个具有自己字段的复杂的对象,传递给嵌套字段的上下文是子对象(sub-object),而不是原来的“数据”对象。换句话说,data.billing_address.addr1 是在这里的范围(译者:这里是直译),然而在之前例子中的 data.addr1 是位置属性。记住:嵌套和列表对象创建一个新属性的范围。

扩展 Flask-RESTful

我们认识到每一个人在 REST 框架上有着不同的需求。Flask-RESTful 试图尽可能的灵活,但是有时候你可能会发现内置的功能不足够满足你的需求。Flask-RESTful 有几个不同的扩展点,这些扩展在这种情况下会有帮助。

内容协商

开箱即用,Flask-RESTful 仅配置为支持 JSON。我们做出这个决定是为了给 API 维护者完全控制 API 格式支持,因此一年来的路上,你不必支持那些使用 API 且用 CSV 表示的人们,甚至你都不知道他们的存在。要添加其它的 mediatypes 到你的 API 中,你需要在 Api 对象中声明你支持的表示。

app = Flask(__name__)
api = restful.Api(app)
 
@api.representation('application/json')
def output_json(data, code, headers=None):
    resp = make_response(json.dumps(data), code)
    resp.headers.extend(headers or {})
    return resp

这些表示函数必须返回一个 Flask Response 对象。

自定义字段 & 输入

一种最常见的 Flask-RESTful 附件功能就是基于你自己数据类型的数据来定义自定义的类型或者字段。

字段

自定义输出字段让你无需直接修改内部对象执行自己的输出格式。所有你必须做的就是继承 Raw 并且实现 format() 方法:

class AllCapsString(fields.Raw):
    def format(self, value):
        return value.upper()
 
 
# example usage
fields = {
    'name': fields.String,
    'all_caps_name': AllCapsString(attribute=name),
}

输入

对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。

def odd_number(value):
    if value % 2 == 0:
        raise ValueError("Value is not odd")
 
    return value

请求解析器在你想要在错误消息中引用名称的情况下将也会允许你访问参数的名称。

def odd_number(value, name):
    if value % 2 == 0:
        raise ValueError("The parameter '{}' is not odd. You gave us the value: {}".format(name, value))
 
    return value

你还可以将公开的参数转换为内部表示:

# maps the strings to their internal integer representation
# 'init' => 0
# 'in-progress' => 1
# 'completed' => 2
 
def task_status(value):
    statuses = [u"init", u"in-progress", u"completed"]
    return statuses.index(value)

然后你可以在你的 RequestParser 中使用这些自定义输入类型:

parser = reqparse.RequestParser()
parser.add_argument('OddNumber', type=odd_number)
parser.add_argument('Status', type=task_status)
args = parser.parse_args()

响应格式

为了支持其它的表示(像 XML,CSV,HTML),你可以使用 representation() 装饰器。你需要在你的 API 中引用它。

api = restful.Api(app)
 
@api.representation('text/csv')
def output_csv(data, code, headers=None):
    pass
    # implement csv output!

这些输出函数有三个参数,data,code,以及 headers。

data 是你从你的资源方法返回的对象,code 是预计的 HTTP 状态码,headers 是设置在响应中任意的 HTTP 头。你的输出函数应该返回一个 Flask 响应对象。

def output_json(data, code, headers=None):
    """Makes a Flask response with a JSON encoded body"""
    resp = make_response(json.dumps(data), code)
    resp.headers.extend(headers or {})
 
    return resp

另外一种实现这一点的就是继承 Api 类并且提供你自己输出函数。

class Api(restful.Api):
    def __init__(self, *args, **kwargs):
        super(Api, self).__init__(*args, **kwargs)
        self.representations = {
            'application/xml': output_xml,
            'text/html': output_html,
            'text/csv': output_csv,
            'application/json': output_json,
        }

资源方法装饰器

Resource() 有一个叫做 method_decorators 的属性。你可以继承 Resource 并且添加你自己的装饰器,该装饰器将会被添加到资源里面所有 method 函数。举例来说,如果你想要为每一个请求建立自定义认证。

def authenticate(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not getattr(func, 'authenticated', True):
            return func(*args, **kwargs)
 
        acct = basic_authentication()  # custom account lookup function
 
        if acct:
            return func(*args, **kwargs)
 
        restful.abort(401)
    return wrapper
 
 
class Resource(restful.Resource):
    method_decorators = [authenticate]   # applies to all inherited resources

由于 Flask-RESTful Resources 实际上是 Flask 视图对象,你也可以使用标准的 flask 视图装饰器

自定义错误处理器

错误处理是一个很棘手的问题。你的 Flask 应用可能身兼数职,然而你要以正确的内容类型以及错误语法处理所有的 Flask-RESTful 错误。

Flask-RESTful 在 Flask-RESTful 路由上发生任何一个 400 或者 500 错误的时候调用 handle_error() 函数,不会干扰到其它的路由。你可能需要你的应用程序在 404 Not Found 错误上返回一个携带正确媒体类型(介质类型)的错误信息;在这种情况下,使用 Api 构造函数的 catch_all_404s 参数。

app = Flask(__name__)
api = flask_restful.Api(app, catch_all_404s=True)

Flask-RESTful 会处理除了自己路由上的错误还有应用程序上所有的 404 错误。

有时候你想在发生错误的时候做一些特别的东西 - 记录到文件,发送邮件,等等。使用 got_request_exception() 方法把自定义错误处理加入到异常。

def log_exception(sender, exception, **extra):
    """ Log an exception to our logging framework """
    sender.logger.debug('Got exception during processing: %s', exception)
 
from flask import got_request_exception
got_request_exception.connect(log_exception, app)

定义自定义错误消息

在一个请求期间遇到某些错误的时候,你可能想返回一个特定的消息以及/或者状态码。你可以告诉 Flask-RESTful 你要如何处理每一个错误/异常,因此你不必在你的 API 代码中编写 try/except 代码块。

errors = {
    'UserAlreadyExistsError': {
        'message': "A user with that username already exists.",
        'status': 409,
    },
    'ResourceDoesNotExist': {
        'message': "A resource with that ID no longer exists.",
        'status': 410,
        'extra': "Any extra information you want.",
    },
}

包含 ‘status’ 键可以设置响应的状态码。如果没有指定的话,默认是 500.

一旦你的 errors 字典定义,简单地把它传给 Api 构造函数。

app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)

中高级用法

本页涉及构建一个稍微复杂的 Flask-RESTful 应用程序,该应用程序将会覆盖到一些最佳练习当你建立一个真实世界的基于 Flask-RESTful 的 API。快速入门 章节适用于开始你的第一个 Flask-RESTful 应用程序,因此如果你是 Flask-RESTful 的新用户,最好查阅该章节。

项目结构

有许多不同的方式来组织你的 Flask-RESTful 应用程序,但是这里我们描述了一个在大型的应用程序中能够很好地扩展并且维持一个不错的文件组织。

最基本的思路就是把你的应用程序分为三个主要部分。路由,资源,以及任何公共的基础部分。

下面是目录结构的一个例子:

myapi/
    __init__.py
    app.py          # this file contains your app and routes
    resources/
        __init__.py
        foo.py      # contains logic for /Foo
        bar.py      # contains logic for /Bar
    common/
        __init__.py
        util.py     # just some common infrastructure

common 文件夹可能只包含一组辅助函数以满足你的应用程序公共的需求。例如,它也可能包含任何自定义输入/输出类型。

在 resource 文件夹中,你只有资源对象。因此这里就是 foo.py 可能的样子:

from flask.ext import restful
 
class Foo(restful.Resource):
    def get(self):
        pass
    def post(self):
        pass

app.py 中的配置就像这样:

from flask import Flask
from flask.ext import restful
from myapi.resources.foo import Foo
from myapi.resources.bar import Bar
from myapi.resources.baz import Baz
 
app = Flask(__name__)
api = restful.Api(app)
 
api.add_resource(Foo, '/Foo', '/Foo/<str:id>')
api.add_resource(Bar, '/Bar', '/Bar/<str:id>')
api.add_resource(Baz, '/Baz', '/Baz/<str:id>')

因为你可能编写一个特别大型或者复杂的 API,这个文件里面会有一个所有路由以及资源的复杂列表。你也可以使用这个文件来设置任何的配置值(before_request,after_request)。基本上,这个文件配置你整个 API。

完整的参数解析示例

在文档的其它地方,我们已经详细地介绍了如何使用 reqparse 的例子。这里我们将设置一个有多个输入参数的资源。我们将定义一个名为 “User” 的资源。

from flask.ext import restful
from flask.ext.restful import fields, marshal_with, reqparse
 
def email(email_str):
    """ return True if email_str is a valid email """
    if valid_email(email):
        return True
    else:
        raise ValidationError("{} is not a valid email")
 
post_parser = reqparse.RequestParser()
post_parser.add_argument(
    'username', dest='username',
    type=str, location='form',
    required=True, help='The user\'s username',
)
post_parser.add_argument(
    'email', dest='email',
    type=email, location='form',
    required=True, help='The user\'s email',
)
post_parser.add_argument(
    'user_priority', dest='user_priority',
    type=int, location='form',
    default=1, choices=range(5), help='The user\'s priority',
)
 
user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'email': fields.String,
    'user_priority': fields.Integer,
    'custom_greeting': fields.FormattedString('Hey there {username}!'),
    'date_created': fields.DateTime,
    'date_updated': fields.DateTime,
    'links': fields.Nested({
        'friends': fields.Url('/Users/{id}/Friends'),
        'posts': fields.Url('Users/{id}/Posts'),
    }),
}
 
class User(restful.Resource):
 
    @marshal_with(user_fields)
    def post(self):
        args = post_parser.parse_args()
        user = create_user(args.username, args.email, args.user_priority)
        return user
 
    @marshal_with(user_fields)
    def get(self, id):
        args = get_parser.parse_args()
        user = fetch_user(id)
        return user

正如你所看到的,我们创建一个 post_parser 专门用来处理解析 POST 请求携带的参数。让我们逐步介绍每一个定义的参数。

post_parser.add_argument(
    'username', dest='username',
    type=str, location='form',
    required=True, help='The user\'s username',
)

username 字段是所有参数中最为普通的。它从 POST 数据中获取一个字符串并且把它转换成一个字符串类型。该参数是必须得(required=True),这就意味着如果不提供改参数,Flask-RESTful 会自动地返回一个消息是’用户名字段是必须‘的 400 错误。

post_parser.add_argument(
    'email', dest='email',
    type=email, location='form',
    required=True, help='The user\'s email',
)

email 字段是一个自定义的 email 类型。在最前面几行中我们定义了一个 email 函数,它接受一个字符串,如果该字符串类型合法的话返回 True,否则会引起一个 ValidationError 异常,该异常明确表示 email 类型不合法。

post_parser.add_argument(
    'user_priority', dest='user_priority',
    type=int, location='form',
    default=1, choices=range(5), help='The user\'s priority',
)

user_priority 类型充分利用了 choices 参数。这就意味着如果提供的 user_priority 值不落在由 choices 参数指定的范围内的话,Flask-RESTful 会自动地以 400 状态码以及一个描述性的错误消息响应。

下面该讨论到输入了。我们也在 user_fields 字典中定义了一些有趣的字段类型用来展示一些特殊的类型。

user_fields = {
    'id': fields.Integer,
    'username': fields.String,
    'email': fields.String,
    'user_priority': fields.Integer,
    'custom_greeting': fields.FormattedString('Hey there {username}!'),
    'date_created': fields.DateTime,
    'date_updated': fields.DateTime,
    'links': fields.Nested({
        'friends': fields.Url('/Users/{id}/Friends', absolute=True),
        'posts': fields.Url('Users/{id}/Posts', absolute=True),
    }),
}

首先,存在一个 fields.FormattedString

'custom_greeting': fields.FormattedString('Hey there {username}!'),

此字段主要用于篡改响应中的值到其它的值。在这种情况下,custom_greeting 将总是包含从 username 字段返回的值。

下一步,检查 fields.Nested

'links': fields.Nested({
    'friends': fields.Url('/Users/{id}/Friends', absolute=True),
    'posts': fields.Url('Users/{id}/Posts', absolute=True),
}),

此字段是用于在响应中创建子对象。在这种情况下,我们要创建一个包含相关对象 urls 的 links 子对象。注意这里我们是使用了 fields.Nested

最后,我们使用了 fields.Url 字段类型。

'friends': fields.Url('/Users/{id}/Friends', absolute=True),
'posts': fields.Url('Users/{id}/Posts', absolute=True),

它接受一个字符串作为参数,它能以我们上面提到的 fields.FormattedString 同样的方式被格式化。传入 absolute=True 确保生成的 Urls 包含主机名。

 

转载于:https://www.cnblogs.com/sanduzxcvbnm/p/9339744.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值