天天看點

【Flask-RESTPlus系列】Part1:快速入門

0x00 内容概覽

  1. Flask-RESTPlus安裝
  2. 快速入門
    1. 初始化
    2. 一個最簡單的API示例
    3. 資源路由
    4. 端點
    5. 參數解析
    6. 資料格式化
    7. 順序保留
    8. 完整例子 

0x01 Flask-RESTPlus安裝

1、Python版本相容性

目前Flask-RESTPlus的最新版本為v0.11.0,支援2.7或3.4+版本的Python。

2、安裝方式

可以通過以下幾種方式來安裝:

pip安裝:$ pip install flask-restplus

easy_install安裝:$ easy_install flask-restplus

離線安裝:首先下載下傳flask-restplus包,然後本地解壓切換到包目錄,使用python setup.py install安裝

安裝開發版:

git clone https://github.com/noirbizarre/flask-restplus.git
cd flask-restplus
pip install -e .[dev,test]      

0x02 快速入門

 本教程假設你已經熟悉了Flask,并已經正常安裝了Flask和Flask-RESTPlus。如果還未安裝Flask-RESTPlus,那麼請參考0x01部分進行安裝。

1、初始化

在使用Flask-RESTPlus之前,需要進行初始化,這一點與Flask的其他擴充是一樣的,通過傳入Flask執行個體進行初始化:

from flask import Flask
from flask_restplus import Api

app = Flask(__name__)
api = Api(app)      

或者使用工廠模式進行初始化:

from flask import Flask
from flask_restplus import Api

api = Api()

app = Flask(__name__)
api.init_app(app)      

2、一個最簡單的API示例

一個最簡單的API示例程式如下:

1 # file:1-Quick-Start.py
 2 
 3 from flask import Flask
 4 from flask_restplus import Resource, Api
 5 
 6 app = Flask(__name__)
 7 api = Api(app)
 8 
 9 @api.route('/hello')
10 class HelloWorld(Resource):
11     def get(self):
12         return {'hello': 'world'}
13 
14 if __name__ == '__main__':
15     app.run(debug=True)      

需要注意的是,此時在程式中我們開啟了Flask的調試模式,即設定了debug=True,這是為了更詳細地列印錯誤資訊,以及確定我們每次修改代碼時,都會自動發現變更并重新啟動運作最新的代碼。不過,生産環境絕對不要開啟調試模式,因為它會使你的背景服務處于被攻擊的風險之中!

此時在PyCharm中運作該程式,正常情況下會列印出以下資訊:

C:\SelfFiles\Install\Python36\python.exe C:/SelfFiles/Codes/Python/codes/Flask-RESTPlus-Tutorial/1_Quick_Start/1-Quick-Start.py

* Restarting with stat

* Debugger is active!

* Debugger PIN: 156-529-095

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

此時在浏覽器中通路http://127.0.0.1:5000/hello會傳回以下結果:

【Flask-RESTPlus系列】Part1:快速入門

或者使用curl工具進行通路:

【Flask-RESTPlus系列】Part1:快速入門

另外,我們也可以在浏覽器中直接通路我們API的根路徑,即http://127.0.0.1:5000,此時會顯示Swagger的界面,裡面包含了我們的Restful API的相應資訊,這就是Flask-RESTPlus的強大之處(當然,其實是Swagger的強大之處):

【Flask-RESTPlus系列】Part1:快速入門

 3、資源路由

 Flask-RESTPlus提供的主要建立對象就是資源。資源建立于Flask可插入視圖(pluggable view)之上,使得我們可以通過在資源上定義方法來很容易地通路多個HTTP方法。下面是一個對todo應用的基本CRUD資源操作的示例:

1 from flask import Flask, request
 2 from flask_restplus import Resource, Api
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
 6 
 7 todos = {}
 8 
 9 @api.route('/<string:todo_id>')
10 class TodoSimple(Resource):
11     def get(self, todo_id):
12         return {todo_id: todos[todo_id]}
13 
14     def put(self, todo_id):
15         todos[todo_id] = request.form['data']
16         return {todo_id: todos[todo_id]}
17 
18 if __name__ == '__main__':
19     app.run(debug=True)      

可以通過curl對其進行通路操作:

【Flask-RESTPlus系列】Part1:快速入門

或者,如果你的Python中安裝了Requests包,那也也可以使用它來進行通路:

【Flask-RESTPlus系列】Part1:快速入門

Flask-RESTPlus了解視圖方法中的多種類型的傳回值。類似于Flask,你可以傳回任何可疊代的類型,它會将該傳回值轉換成響應對象(response),包括原始的Flask響應對象。Flask-RESTPlus還提供了設定響應碼和響應頭的功能,這一點可以通過使用多個傳回值來實作,如下所示:

class Todo1(Resource):
    def get(self):
        # 預設為200 OK
        return {'task': 'Hello world'}

class Todo2(Resource):
    def get(self):
        # 設定響應碼為201
        return {'task': 'Hello world'}, 201

class Todo3(Resource):
    def get(self):
        # 設定響應碼為201,并傳回自定義的響應頭
        return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}      

4、端點(Endpoints)

大多數情況下,某個資源都會有多個URL。是以,我們可以向Api對象的add_resource()方法或route()裝飾器中傳入多個URL,這樣每個URL都将會路由到該資源上:

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

# 或者下面裝飾器方式,二者等價

@api.route('/hello', '/world')
class HelloWorld(Resource):
    pass      

另外,也可以将URL中的部分内容設定成變量,以此來比對資源方法,如下所示:

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

# 或者下面裝飾器方式,二者等價

@api.route('/todo/<int:todo_id>', endpoint='todo_ep')
class HelloWorld(Resource):
    pass

# 這樣,URL為/todo/1、/todo/2等以/todo/加一個int型整數的URL都可以路由到該資源      

注意:如果一個請求(request)與應用的任何端點都不比對,那麼Flask-RESTPlus将會傳回一個404錯誤資訊,并給出其他與所請求端點最比對的建議資訊。不過,我們可以通過在程式配置中設定ERROR_404_HELP為False來關閉該功能。

未關閉時程式如下:

1 from flask import Flask, request
 2 from flask_restplus import Resource, Api
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
 6 
 7 
 8 todos = {}
 9 
10 @api.route('/<string:todo_id>')
11 class TodoSimple(Resource):
12     def get(self, todo_id):
13         return {todo_id: todos[todo_id]}
14 
15     def put(self, todo_id):
16         todos[todo_id] = request.form['data']
17         return {todo_id: todos[todo_id]}
18 
19 if __name__ == '__main__':
20     app.run(debug=True)      

運作改程式并在浏覽器中通路http://localhost:5000/hello/hello,結果如下:

【Flask-RESTPlus系列】Part1:快速入門

設定ERROR_404_HELP為False後的程式為:

1 from flask import Flask, request
 2 from flask_restplus import Resource, Api
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
 6 app.config['ERROR_404_HELP'] = False
 7 
 8 todos = {}
 9 
10 @api.route('/<string:todo_id>')
11 class TodoSimple(Resource):
12     def get(self, todo_id):
13         return {todo_id: todos[todo_id]}
14 
15     def put(self, todo_id):
16         todos[todo_id] = request.form['data']
17         return {todo_id: todos[todo_id]}
18 
19 if __name__ == '__main__':
20     app.run(debug=True)      

再次運作并通路http://localhost:5000/hello/hello,結果如下:

【Flask-RESTPlus系列】Part1:快速入門

此處兩種情況傳回結果一緻,尚未嘗試出給出相近端點的建議資訊,也許是我沒用使用對,後續再補充。

5、參數解析(Argument Parsing) 

盡管Flask提供了容易的方式來通路請求資料(例如,查詢字元串querystring或者POST表單編碼資料),但驗證表單資料仍舊是一件令人頭疼的事。Flask-RESTPlus内置支援對請求資料的驗證,這一功能是通過使用一個類似于argparse的庫來實作的,如下:

from flask_restplus import reqparse

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

注意:與argparse子產品不同的是,parse_args()傳回的是一個Python字典,而不是自定義資料結構。

使用RequestParser類還能擷取完整的錯誤資訊。如果一個參數未驗證通過,Flask-RESTPlus将響應一個400壞請求,以及一個高亮錯誤資訊的響應。示例程式如下:

1 from flask import Flask, request
 2 from flask_restplus import Resource, Api,reqparse
 3 
 4 app = Flask(__name__)
 5 
 6 api = Api(app)
 7 
 8 from flask_restplus import reqparse
 9 
10 parser = reqparse.RequestParser()
11 parser.add_argument('rate', type=int,required=True,help='Rate to charge for this resource')
12 
13 
14 todos = {
15     '1':'eat',
16     '2':'sleep'
17 }
18 
19 @api.route('/<string:todo_id>')
20 class TodoSimple(Resource):
21     def get(self, todo_id):
22         return {todo_id: todos[todo_id]}
23 
24     def put(self, todo_id):
25         args = parser.parse_args()
26         todos[todo_id] = request.form['data']
27         return {todo_id: todos[todo_id]}
28 
29 if __name__ == '__main__':
30     app.run(debug=True)      

其中,parser.add_argument('rate', type=int,required=True,help='Rate to charge for this resource')表示,參數名為rate,資料類型為int,請求時必須發送此參數,如果驗證不通過時将會傳回help指定的資訊。

運作程式并使用curl進行通路,分别驗證以下幾種情況:

  • 提供rate值,但不是int型(驗證不通過)
  • 提供rate值,且是int型(驗證通過)
  • 不提供rate值(驗證不通過)

結果分别如下:

【Flask-RESTPlus系列】Part1:快速入門

另外,以參數strict=True調用parse_args()能夠保證如果請求中包含了解析器中未定義的參數時,将會抛出一個錯誤。示例程式如下:

1 from flask import Flask, request
 2 from flask_restplus import Resource, Api,reqparse
 3 
 4 app = Flask(__name__)
 5 
 6 api = Api(app)
 7 
 8 from flask_restplus import reqparse
 9 
10 parser = reqparse.RequestParser()
11 parser.add_argument('rate', type=int,required=True,help='Rate to charge for this resource')
12 
13 
14 todos = {
15     '1':'eat',
16     '2':'sleep'
17 }
18 
19 @api.route('/<string:todo_id>')
20 class TodoSimple(Resource):
21     def get(self, todo_id):
22         return {todo_id: todos[todo_id]}
23 
24     def put(self, todo_id):
25         args = parser.parse_args(strict=True)
26         todos[todo_id] = todo_id
27         return {todo_id: todos[todo_id]}
28 
29 if __name__ == '__main__':
30     app.run(debug=True)      

此時,運作該程式并使用curl通路,結果如下:

【Flask-RESTPlus系列】Part1:快速入門

 6、資料格式化(Data Formatting)

預設情況下,在傳回的可疊代對象中的所有字段都會原樣傳回。雖然在處理Python基本資料結構時這種方式很不錯,但是當涉及到對象時将會變得非常棘手。為了解決這個問題,Flask-RESTPlus提供了fields子產品和marshal_with()裝飾器。類似于Django ORM和WTForm,你可以使用fields子產品來描述響應的資料結構。示例程式如下:

1 from flask import Flask
 2 from flask_restplus import fields, Api, Resource
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
 6 
 7 model = api.model('Model', {
 8     'task': fields.String,
 9     'uri': fields.Url('todo_ep',absolute=True) # absolute參數表示生成的url是否是絕對路徑
10 })
11 
12 class TodoDao(object):
13     def __init__(self, todo_id, task):
14         self.todo_id = todo_id
15         self.task = task
16 
17         # 該字段不會發送到響應結果中
18         self.status = 'active'
19 
20 @api.route('/todo',endpoint='todo_ep')
21 class Todo(Resource):
22     @api.marshal_with(model)
23     def get(self, **kwargs):
24         return TodoDao(todo_id='my_todo', task='Remember the milk')
25 
26 if __name__ == '__main__':
27     app.run(debug=True)      

運作上述程式并使用curl通路結果如下:

【Flask-RESTPlus系列】Part1:快速入門

上述示例接受了一個Python對象,并将其進行結構轉換。而marshal_with()裝飾器就是用來對結果按照model的結構進行轉換的,從上面的結果和代碼中可以知道,我們僅僅從TodoDao對象中提取了task字段的值,而model中的fields.Url字段是一個特殊字段,它接受一個端點名,并在響應中生成該端點名對應的URL。此外,使用marshal_with()裝飾器還可以以swagger規範對輸出進行歸檔。fields子產品中包含了你所需要的大多數類型,詳細資訊可以檢視fields子產品的說明文檔。

7、順序保留

 預設情況下,字段順序并未得到保留,因為它會損耗性能。不過,如果你确實需要保留字段順序,那麼可以向類或函數傳入一個ordered=True的參數項,以此強制進行順序保留:

  • Api全局保留:api = Api(ordered = True)
  • Namespace全局保留:ns = Namespace(ordered=True)
  • marshal()局部保留:return marshal(data, fields, ordered=True)

本例中隻舉例局部保留方式的使用方法,程式如下:

1 from flask import Flask
 2 from flask_restplus import fields, Api, Resource
 3 
 4 app = Flask(__name__)
 5 api = Api(app)
 6 
 7 model = api.model('Model', {
 8     'task': fields.String,
 9     'uri': fields.Url('todo_ep',absolute=True), # absolute參數表示生成的url是否是絕對路徑
10     'developer':fields.String(default='jack')
11 })
12 
13 class TodoDao(object):
14     def __init__(self, todo_id, task, developer):
15         self.todo_id = todo_id
16         self.task = task
17         self.developer = developer
18 
19         # 該字段不會發送到響應結果中
20         self.status = 'active'
21 
22 @api.route('/todo',endpoint='todo_ep')
23 class Todo(Resource):
24     # @api.marshal_with(model)
25     def get(self, **kwargs):
26         return api.marshal(TodoDao(todo_id='my_todo', task='Remember the milk', developer='Tom'),model,ordered=False)
27 
28 if __name__ == '__main__':
29     app.run(debug=True)      

運作并使用curl進行通路,結果如下:

【Flask-RESTPlus系列】Part1:快速入門

8、完整例子

1 from flask import Flask
 2 from flask_restplus import Api, Resource, fields
 3 from werkzeug.contrib.fixers import ProxyFix
 4 
 5 app = Flask(__name__)
 6 app.wsgi_app = ProxyFix(app.wsgi_app)
 7 
 8 api = Api(app, version='1.0', title='TodoMVC API',
 9     description='A simple TodoMVC API',
10 )
11 
12 # 定義命名空間
13 ns = api.namespace('todos', description='TODO operations')
14 
15 todo = api.model('Todo', {
16     'id': fields.Integer(readOnly=True, description='The task unique identifier'),
17     'task': fields.String(required=True, description='The task details')
18 })
19 
20 
21 class TodoDAO(object):
22     def __init__(self):
23         self.counter = 0
24         self.todos = []
25 
26     def get(self, id):
27         for todo in self.todos:
28             if todo['id'] == id:
29                 return todo
30         api.abort(404, "Todo {} doesn't exist".format(id))
31 
32     def create(self, data):
33         todo = data
34         todo['id'] = self.counter = self.counter + 1
35         self.todos.append(todo)
36         return todo
37 
38     def update(self, id, data):
39         todo = self.get(id)
40         todo.update(data)
41         return todo
42 
43     def delete(self, id):
44         todo = self.get(id)
45         self.todos.remove(todo)
46 
47 
48 DAO = TodoDAO()
49 DAO.create({'task': 'Build an API'})
50 DAO.create({'task': '?????'})
51 DAO.create({'task': 'profit!'})
52 
53 
54 @ns.route('/')
55 class TodoList(Resource):
56     '''擷取所有todos元素,并允許通過POST來添加新的task'''
57     @ns.doc('list_todos')
58     @ns.marshal_list_with(todo)
59     def get(self):
60         '''傳回所有task'''
61         return DAO.todos
62 
63     @ns.doc('create_todo')
64     @ns.expect(todo)
65     @ns.marshal_with(todo, code=201)
66     def post(self):
67         '''建立一個新的task'''
68         return DAO.create(api.payload), 201
69 
70 
71 @ns.route('/<int:id>')
72 @ns.response(404, 'Todo not found')
73 @ns.param('id', 'The task identifier')
74 class Todo(Resource):
75     '''擷取單個todo項,并允許删除操作'''
76     @ns.doc('get_todo')
77     @ns.marshal_with(todo)
78     def get(self, id):
79         '''擷取id指定的todo項'''
80         return DAO.get(id)
81 
82     @ns.doc('delete_todo')
83     @ns.response(204, 'Todo deleted')
84     def delete(self, id):
85         '''根據id删除對應的task'''
86         DAO.delete(id)
87         return '', 204
88 
89     @ns.expect(todo)
90     @ns.marshal_with(todo)
91     def put(self, id):
92         '''更新id指定的task'''
93         return DAO.update(id, api.payload)
94 
95 
96 if __name__ == '__main__':
97     app.run(debug=True)      

 更多其他示例參考GitHub。

0x03 參考連結

  • http://flask-restplus.readthedocs.io/en/stable/quickstart.html
  • http://flask-restplus.readthedocs.io/en/stable/example.html