'How to parse a multipart form-data that contains both files and normal fields in webargs?

I need to parse a multipart form-data with attached file using webargs. At this moment I have the next model:

RAW_ARGS = {
    'file': fields.Field(
        required=True,
        validate=lambda file: file.mimetype == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        location='files'),
    'currency': fields.Int(required=True, validate=validate.OneOf([
        ECurrency.EUR.value,
        ECurrency.RUB.value,
        ECurrency.USD.value
    ]), location='form')
}

class RawResource(Resource):

    @token_required
    @use_args(RAW_ARGS)
    def post(self, args):
        return '', 204

But on request I get The request was well-formed but was unable to be followed due to semantic errors. error with 422 HTTP-status code.

Below is a copy of request from Chrome Network:

Request URL: http://localhost:5000/api/v1/raw
Request Method: POST
Status Code: 422 UNPROCESSABLE ENTITY
Remote Address: 127.0.0.1:5000
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Origin: http://localhost:4200
Content-Length: 186
Content-Type: application/json
Date: Tue, 28 Apr 2020 12:41:08 GMT
Server: Werkzeug/1.0.1 Python/3.8.1
Vary: Origin
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Authorization: Bearer <token>
Connection: keep-alive
Content-Length: 405453
Content-Type: multipart/form-data
Host: localhost:5000
Origin: http://localhost:4200
Referer: http://localhost:4200/upload
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36
------WebKitFormBoundary5KsexIuuVJnu3TnU
Content-Disposition: form-data; name="currency"

840
------WebKitFormBoundary5KsexIuuVJnu3TnU
Content-Disposition: form-data; name="file"; filename="test.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet


------WebKitFormBoundary5KsexIuuVJnu3TnU--

What's a right way to parse a multipart form-data?

P.S. My server is Flask app.



Solution 1:[1]

write @use_args twice, pass it twice. I have edited your code accordingly

RAW_ARGS = {
    'file': fields.Field(
        required=True,
        validate=lambda file: file.mimetype == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        location='files'),
    'currency': fields.Int(required=True, validate=validate.OneOf([
        ECurrency.EUR.value,
        ECurrency.RUB.value,
        ECurrency.USD.value
    ]), location='form')
}
class RawResource(Resource):
    @token_required
    @use_args(RAW_ARGS['file'])
    @use_args(RAW_ARGS['currency'])
    def post(self, args_file, args_currency):
        return '', 204

Solution 2:[2]

What you want is a custom data loader for both form and files location (i.e. form_and_files). I just implemented it in this PR:

from webargs.multidictproxy import MultiDictProxy


@parser.location_loader('form_and_files')
def load_form_and_files(request, schema):
    form_and_files_data = request.files.copy()
    form_and_files_data.update(request.form)
    return MultiDictProxy(form_and_files_data, schema)

Then you can declare the location form_and_files in your view's use_args decorator:

@use_args(RAW_ARGS, location='form_and_files_data')

See more detail in webargs docs.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ashutosh Sharma
Solution 2 Grey Li