【Flask-RESTPlus系列】Part3:请求解析

 

0x00 内容概览

  1. 请求解析
    1. 基本参数
    2. 必需参数
    3. 多值和列表
    4. 其他目标
    5. 参数位置
    6. 参数多个位置
    7. 高级类型处理
    8. 解析器继承
    9. 文件上传
    10. 错误处理
    11. 错误消息
  2. 参考链接

0x01 请求解析

注意:Flask-RESTPlus的整个请求解析器部分将被移除,并将替换成关于集成其他更善于处理输入、输出的包(例如marshmallow)的说明文档。但是考虑到已经被废弃,它将一直维护到2.0版本。如果你现在有代码使用它,并希望继续这样做,那么也无需担心,因为它不会很快消失。

Flask-RESTPlus的请求解析接口reqparse是模仿argparse接口实现的。它的设计目的是对Flask中flask.request对象上的任何变量提供简单和统一的访问方式。

1、基本参数

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

from flask_restplus import reqparse

parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name') # Python3中默认类型为字符串
args = parser.parse_args()

注意:默认参数类型是unicode字符串。在Python3中为str类型,而在Python2中为unicode。

如果指定了help变量的值,那么在解析请求时,如果出现了类型错误,那么会将它渲染为错误信息。如果未指定help信息,那么默认的行为是返回类型错误信息本身。可以查看“11、错误信息”部分以了解更多细节。

注意:默认情况下,参数不是必需的。并且,如果请求中提供的某些参数不是RequestParser的部分内容,那么这些参数将会被忽略。

注意:请求解析器中声明的参数,如果在请求中并未设置这些参数值,那么它们将会默认设置为None。

2、必需参数

如果需要确保某个参数必须提供,那么可以在调用add_argument()时传入required=True的参数项:

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

此时,如果请求中未提供该参数,那么将会返回错误信息。

3、多值和列表

如果想为某个key接受多个值以构成列表,那么可以传入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']

如果期望一个逗号分隔的列表,那么可以使用action='split':

parser.add_argument('fruits', action='split')

此时的查询格式如下所示:

curl http://api.example.com -d "fruits=apple,lemon,cherry"

而程序中获取到的参数如下所示:

args = parser.parse_args()
args['fruits']    # ['apple', 'lemon', 'cherry']

4、其他目标

 如果期望参数一旦被解析,就将其存储为其他名字,那么可以使用dest参数:

parser.add_argument('name', dest='public_name')

args = parser.parse_args()
args['public_name']

5、参数位置

默认情况下,RequestParser尝试从flask.Request.values和flask.Request.json中解析值。

在add_argument()中使用location参数来指定获取值的其他位置。可以使用flask.Request上的任何变量,例如:

# 仅仅在POST body中查找
parser.add_argument('name', type=int, location='form')

# 仅仅在querystring中查找
parser.add_argument('PageSize', type=int, location='args')

# 从请求头中查找
parser.add_argument('User-Agent', location='headers')

# 从http cookies中查找
parser.add_argument('session_id', location='cookies')

# 从上传文件中查找
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')

注意:当location='json'时只能使用type=list,点击此处查看更多

注意:使用location='form'既能验证表单数据,又能为表单字段文档化。

6、参数多位置

为location参数赋一个列表就能为参数指定多个位置:

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

当指定多个参数位置时,那么所有指定位置的参数将会组成一个MultiDict。其中,列表中最后位置处的参数将会优先存储在结果集中。

如果参数位置列表中包含headers位置,那么参数名将变成对大小写敏感,并且必须匹配它们的标题大小写名称(见str.title())。

指定location='headers'(而不是作为列表的某个元素)将保留大小写不敏感的特性。

7、高级类型处理

 有时,我们需要其他原始类型来处理输入验证问题。为此,inputs模块中提供了一些常用的类型处理方法,如下:

  • boolean()用于广泛的布尔值处理
  • ipv4()和ipv6()用于IP地址
  • date_from_iso8601()和datetime_from_iso8601()用于ISO8601 date和datetime处理

只需要使用它们作为type参数的值即可:

parser.add_argument('flag', type=inputs.boolean)

查看inputs文档以了解所有可用的输入类型。

另外,我们也可以编写自己的输入类型:

def my_type(value):
    '''解析类型'''
    if not condition:
        raise ValueError('This is not my type')
    return parse(value)

# Swagger文档化
my_type.__schema__ = {'type': 'string', 'format': 'my-custom-format'}

8、解析器继承

 很多情况下,我们都需要为不同的资源指定不同的解析器。不过,如果这些不同的解析器之间存在大量相同的字段的话,将会存在大量重复编码的问题。为此,我们可以编写一个父解析器,父解析器中包含所有共同的参数,然后利用copy()方法来扩展解析器。另外,也可以利用replace_argument()来覆写父解析器中的任何参数,或者利用remove_argument()完全移除父解析器中的某个参数。例如:

from flask_restplus import reqparse

parser = reqparse.RequestParser()
parser.add_argument('foo', type=int)

parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
# 此时,parser_copy中同时包含'foo'和'bar'参数


parser_copy.replace_argument('foo', required=True, location='json')
# 此时,'foo'参数变成了一个必需的str类型的参数,并且查找位置为json;而不再是父解析器中定义的int类型的可选参数


parser_copy.remove_argument('foo')
# 此时,parser_copy中不再包含'foo'参数

9、文件上传

 为了利用RequestParser处理文件上传问题,我们需要将location变量值设置为files,并设置type值为FileStorage。如下所示:

from werkzeug.datastructures import FileStorage

upload_parser = api.parser()
upload_parser.add_argument('file', location='files',
                           type=FileStorage, required=True)


@api.route('/upload/')
@api.expect(upload_parser)
class Upload(Resource):
    def post(self):
        uploaded_file = args['file']  # 这是FileStorage实例
        url = do_something_with_file(uploaded_file)
        return {'url': url}, 201

10、错误处理

 RequestParser处理错误的默认方式是在第一个错误产生时中断。当我们拥有需要花费一定时间来处理的参数时,这种方式是有好处的。然而,通常来说,将所有产生的错误都绑定在一起,然后同时一次性返回给客户端,这种方式则更加友好。这种方式既可以在Flask应用级别指定,也可以在特定的RequestParser实例级别指定。为了调用一个包含错误绑定选项的RequestParser,需要传入参数bundle_errors。例如:

from flask_restplus import reqparse

parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

# 如果某个请求中同时不包含'foo'和'bar',那么返回的错误将看起来如下所示:

{
    "message":  {
        "foo": "foo error message",
        "bar": "bar error message"
    }
}

# 默认操作将仅仅返回第一个错误
parser = RequestParser()
parser.add_argument('foo', type=int, required=True)
parser.add_argument('bar', type=int, required=True)

{
    "message":  {
        "foo": "foo error message"
    }
}

应用级别的配置key为“BUNDLE_ERRORS”。例如:

from flask import Flask

app = Flask(__name__)
app.config['BUNDLE_ERRORS'] = True

警告:BUNDLE_ERRORS是一个全局设置,它将覆盖每个RequestParser实例中的bundle_errors选项值。

11、错误消息

每个字段的错误消息都可以通过在Argument(也包括RequestParser.add_argument)中使用help参数来自定义。

如果没有提供help参数,那么该字段的错误消息将会是类型错误本身的字符串表示。如果提供了help参数,那么错误消息将会是help参数的值。

help可能包含一个插入的符号{error_msg},它将会替换成类型错误的字符串表示。这种方式能够实现自定义错误消息,同时保留原始的错误消息。如下所示:

from flask_restplus import reqparse


parser = reqparse.RequestParser()
parser.add_argument(
    'foo',
    choices=('one', 'two'),
    help='Bad choice: {error_msg}'
)


# 如果请求中的'foo'参数值为'three',那么错误信息将会如下所示:
{
    "message":  {
        "foo": "Bad choice: three is not a valid choice",
    }
}

0x02 参考链接

 

posted @ 2018-06-10 23:48  HackHan  阅读(8413)  评论(0编辑  收藏  举报