胖蔡说技术
随便扯扯

Flask-RESTful插件支持快速创建RESTful APIS

今天来学习下Flask-RESTful插件,该插件支持在Flask框架快速创建RESTful样式APIS。它是一种轻量级抽象,可与您现有的 ORM/库一起使用。 Flask-RESTful 鼓励以最少的设置进行最佳实践。如果您熟悉 Flask、Flask-RESTful 应该很容易上手。

图1.1 RESTful API

发布版本

  • 0.0.1:2012/6/6发布
  • 0.1:2012/10/19发布
  • 0.1.1:2012/11/20发布
  • 0.1.2:2013/1/9发布
  • ……
  • 0.3.8:2020/2/6发布
  • 0.3.9:2021/3/18发布

安装

可以使用pip安装flask-restful,使用命令如下:

$ pip install flask-restful

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

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

Flask-RESTful 需要 Python 版本 2.7、3.4、3.5、3.6 3.7

示例

如下给出一个简单的示例来了解如何使用Flask-restful插件:

from flask import Flask
from flask_restful import Resource, Api

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

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)

将其保存为 api.py 并使用 Python 解释器运行它。请注意,我们启用了 Flask 调试模式以提供代码重新加载和更好的错误消息。

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

现在打开一个新提示以使用 curl 测试您的 API

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

Resource

Flask-restful基于Resource构建组件,资源建立在 Flask 可插入视图之上,让您只需在资源上定义方法即可轻松访问多个 HTTP 方法。 todo 应用程序的基本 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"}

或者从 python 如果你安装了请求库:

>>> 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'}

端点

很多时候在一个 API 中,您的资源会有多个 URL。您可以将多个 URL 传递给 Api 对象上的 add_resource() 方法。每一个都将被路由到您的资源。

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

您还可以将部分路径作为变量匹配到您的资源方法。

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

参数解析

虽然 Flask 提供了对请求数据(即查询字符串或 POST 表单编码数据)的轻松访问,但验证表单数据仍然很痛苦。 Flask-RESTful 使用类似于 argparse 的库内置了对请求数据验证的支持。

from flask_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 Bad Request 和一个突出显示错误的响应。

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

inputs 模块提供了许多包含的通用转换函数,例如 inputs.date() inputs.url()

使用 strict=True 调用 parse_args 可确保在请求包含您的解析器未定义的参数时抛出错误。

args = parser.parse_args(strict=True)

数据格式

默认情况下,您的返回峰值中的所有字段都将呈现为as-is。虽然您只是在处理Python数据结构时效果很好,但在使用对象时,它可能会变得非常令人沮丧。为了解决这个问题,Flask-RESTful捕获提供了字段模块和元marshal_with()装饰器。类似于Django OrmWTForm,您使用fields 模块来描述响应的结构。

from flask_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 描述的转换。从对象中提取的唯一字段是任务。 fields.Url 字段是一个特殊字段,它采用端点名称并在响应中为该端点生成 URL。您需要的许多字段类型已包含在内。有关完整列表,请参阅fields 指南。

完整示例

将此示例保存在 api.py 中:

from flask import Flask
from flask_restful import reqparse, abort, Api, Resource

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')


# Todo
# shows a single todo item and lets you delete a todo item
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(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的请求解析界面Reqparse是按照ArgParse接口建模的。它旨在对flask.request提供对任何变量的简单均匀访问。

基本参数

这是请求解析器的一个简单示例。它在 flask.Request.values 字典中寻找两个参数:一个整数和一个字符串。

from flask_restful import reqparse

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

如果您指定帮助值,则在解析它时出现类型错误时,它将呈现为错误消息。如果不指定帮助消息,则默认行为是从类型错误本身返回消息。

必需的参数

要要求为参数传递值,只需将 required=True 添加到对 add_argument() 的调用中。

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

多个值和列表

如果要接受键作为列表的多个值,则可以传递action='append'

parser.add_argument('name', 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

如果出于某种原因,RequestParser尝试从flask.Request,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', location='headers')

# From http cookies
parser.add_argument('session_id', location='cookies')

# From file uploads
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')

输出字段

Flask-RESTful 提供了一种简单的方法来控制您在响应中实际呈现的数据。使用 fields 模块,您可以在资源中使用您想要的任何对象(ORM 模型/自定义类等)。字段还允许您格式化和过滤响应,因此您不必担心暴露内部数据结构。

基础用法

您可以定义字段的 dict OrderedDict,其键是要呈现的对象上的属性或键的名称,其值是将格式化并返回该字段值的类。此示例包含三个字段:两个是String,一个是DateTime,格式为 RFC 822 日期字符串(也支持 ISO 8601

from flask_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

重命名属性

通常,您面向公众的字段名称与您的内部字段名称不同。要配置此映射,请使用attribute关键字参数。

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

lambda(或任何可调用的)也可以指定为属性:

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

也可以使用属性访问嵌套属性:

fields = {
    'name': fields.String(attribute='people_list.0.person_dictionary.name'),
    'address': fields.String,
}

默认值

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

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

自定义字段和多个值

有时您有自己的自定义格式需求。您可以子类化 fields.Raw 类并实现格式功能。当一个属性存储多条信息时,这尤其有用。例如一个位字段,其各个位代表不同的值。您可以使用字段将单个属性多路复用为多个输出值。

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,
}

扩展 Flask-RESTful

我们意识到每个人对 REST 框架都有不同的需求。 Flask-RESTful 试图尽可能灵活,但有时您可能会发现内置功能不足以满足您的需求。 Flask-RESTful 有几个不同的扩展点可以在这种情况下提供帮助。

内容协商

开箱即用, Flask-RESTful 仅配置为支持JSON。我们决定使API维护者完全控制API格式支持;因此,您不必使用您不知道存在的APICSV代表来支持人们。要在API中添加其他介体,您需要在API对象上声明所支持的表示形式。

app = Flask(__name__)
api = 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 对象。通过在应用程序配置中提供 RESTFUL_JSON 属性,可以配置默认 Flask-RESTful JSON 表示如何格式化 JSON。此设置是一个字典,其键对应于 json.dumps() 的关键字参数。

class MyConfig(object):
    RESTFUL_JSON = {'separators': (', ', ': '),
                    'indent': 2,
                    'cls': MyCustomEncoder}

自定义字段和输入

Flask-RESTful 最常见的添加之一是根据您自己的数据类型定义自定义类型或字段。

Fields

自定义输出字段可让您执行自己的输出格式,而无需直接修改内部对象。您要做的就是子类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),
}

Inputs

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

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 = Api(app)

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

实现此目的的另一种方法是继承 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 并添加您自己的装饰器,这些装饰器将被添加到资源中的所有方法函数中。例如,如果您想在每个请求中构建自定义身份验证。

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)

        flask_restful.abort(401)
    return wrapper


class Resource(flask_restful.Resource):
    method_decorators = [authenticate]   # applies to all inherited resources
赞(1) 打赏
转载请附上原文出处链接:胖蔡说技术 » Flask-RESTful插件支持快速创建RESTful APIS
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏