• 【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:SelfFilesInstallPython36python.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会返回以下结果:

    或者使用curl工具进行访问:

    另外,我们也可以在浏览器中直接访问我们API的根路径,即http://127.0.0.1:5000,此时会显示Swagger的界面,里面包含了我们的Restful API的相应信息,这就是Flask-RESTPlus的强大之处(当然,其实是Swagger的强大之处):

     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对其进行访问操作:

    或者,如果你的Python中安装了Requests包,那也也可以使用它来进行访问:

    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,结果如下:

    设置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,结果如下:

     

    此处两种情况返回结果一致,尚未尝试出给出相近端点的建议信息,也许是我没用使用对,后续再补充。

    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值(验证不通过)

    结果分别如下:

    另外,以参数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访问,结果如下:

     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访问结果如下:

    上述示例接受了一个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进行访问,结果如下:

    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 参考链接

  • 相关阅读:
    PrintWriter、PrintStream的苦头 缓冲区问题
    BufferedImage与byte[]互转
    求两个日期的间隔天数
    Timer和TimerTask详解
    Java连接Access数据库
    根据value字段对map进行排序
    java collections读书笔记(3)Arrays
    java collections读书笔记(4) stack
    运行时异常与一般异常有何异同?(转)
    java collections读书笔记(7) bitset
  • 原文地址:https://www.cnblogs.com/leejack/p/9162367.html
Copyright © 2020-2023  润新知