Flask-RESTful(轉載)

Flask-RESTful 是一個 Flask 擴展,它添加了快速構建 REST APIs 的支持。它固然也是一個可以跟你現有的ORM/庫協同工做的輕量級的擴展。Flask-RESTful 鼓勵以最小設置的最佳實踐。若是你熟悉 Flask 的話,Flask-RESTful 應該很容易上手。html

用戶指南python

這部分文檔將向你展現如何在 Flask 中使用 Flask-RESTful。git

API 參考github

若是你正在查詢特定函數,類或者方法的信息,這部分文檔就是爲你準備的。shell

其它注意事項數據庫

關於這個項目的法律信息請參閱 Flask 的 licensejson

安裝

使用 pip 安裝 Flask-RESTful:flask

pip install flask-restful

開發的版本能夠從 GitHub 上的頁面 下載api

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 包含主機名。

相關文章
相關標籤/搜索