简体   繁体   中英

Flask restx MarshallingError incorrect formatting

Problem context

I'm experiencing some unexpected behaviour with MarshallingError , when generating api documentation using flask-restx. I have a custom flask-restx field definition, like below.

class EnumField(StringMixin, Raw):

    def __init__(self, enum_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.enum_type = enum_type

    def format(self, value):
        try:
            return self.enum_type(value)
        except ValueError as e:
            raise MarshallingError(e)

    def output(self, key, obj, **kwargs):
        return self.format(obj[key]).value

where enum_type is something simple like

class DemoEnum(Enum):
    a = 'a'
    b = 'b'
    c = 'c'

This is then packaged inside a restx api.model , which looks like the following.

model = api.model('Demo', {"name": EnumField(enum_type=DemoEnum, required=True)})

Issue

When I enter an integer into name , as expected, I'm getting a nice error like below.

{
  "errors": {
    "name": "1 is not of type 'string'"
  },
  "message": "Input payload validation failed"
}

However, when I then enter a value that is not in my enum ("d" for instance), the error seems to be caught in my format definition, however, MarshallingError isn't hiding all of the internal errors as expected. Here's a short snippet of what is being output.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>MarshallingError: 'string' is not a valid DemoEnum // Werkzeug Debugger</title>
...

Quesions

  1. Is this expected behaviour?
  2. Is it possible to return a cleaner error, like the one shown above? My understanding was that my definition of format should achieve this?

Full application for testing

from flask_restx import Api, Resource
from flask_restx.fields import Raw, StringMixin, MarshallingError

from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

from enum import Enum

# =============================================================================
# Custom EnumField and Enum
# =============================================================================

class EnumField(StringMixin, Raw):

    def __init__(self, enum_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.enum_type = enum_type

    def format(self, value):
        try:
            return self.enum_type(value)
        except ValueError as e:
            raise MarshallingError(e)

    def output(self, key, obj, **kwargs):
        return self.format(obj[key]).value

class DemoEnum(Enum):
    a = 'a'
    b = 'b'
    c = 'c'

# =============================================================================
# Demo restx model
# =============================================================================

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(app, version='1.0', title='Test API', validate=True)

ns = api.namespace('demo')

model = api.model('Demo', {
    "name": EnumField(enum_type=DemoEnum, required=True)
})

# =============================================================================
# Api endpoint
# =============================================================================

@ns.route('/')
class Demo(Resource):
    @ns.doc('create_demo')
    @ns.expect(model, validate=True)  # validate checks the input is provided
    @ns.marshal_with(model, code=201)
    def post(self):
        '''Create demo'''
        return api.payload

if __name__ == '__main__':
    app.run(debug=True)
  1. Is this expected behaviour?

Yes, because you're function is not aborting correctly or returning anything.

  1. Is it possible to return a cleaner error, like the one shown above? My understanding was that my definition of format should achieve this?

Yes, you can rescue the error like you were doing, then return your own message and make sure it aborts properly using Flask's abort

Try this:

from flask import Flask, abort


def output(self, key, obj, **kwargs):
    try:
        return self.format(obj[key])
    except (ValueError, MarshallingError) as e:
        return abort(400,  f'Unable to marshal field. errors: [{key}: {str(e)}]')

The output for this example would be a 400 error formatted:

{
  "message": "Unable to marshal field. errors: [name: 'd' is not a valid DemoEnum]"
}

This will not effect the error messaging from the expect decorator, ie. if you enter 1 for the name field you will receive the same message as before.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM