Reputation: 1811
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)})
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>
...
format
should achieve this?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)
Upvotes: 1
Views: 1186
Reputation: 1117
Yes, because you're function is not aborting correctly or returning anything.
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.
Upvotes: 1