天天看點

【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 參考連結

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