apryor6 / flask_accepts

Easy, opinionated Flask input/output handling mixing Marshmallow with flask-restx
BSD 3-Clause "New" or "Revised" License
169 stars 40 forks source link

Swagger fails to load when a schema does not have a default set in one of the fields. #103

Open zanpeeters opened 3 years ago

zanpeeters commented 3 years ago

Swagger fails to load when a marshmallow schema loaded with @responds contains fields without the default= parameter. If I use fields.String(default=None) in the example below, Swagger loads normally. This started happening after I upgraded flask-restx to v0.3.0.

This problem seems to come from the Models section of the Swagger docs. If I remove api=api in @responds, Swagger loads without error, but the Models section is missing.

In flask_accepts/utils.py:

def _ma_field_to_fr_field(value: ma.Field) -> dict:
    fr_field_parameters = {}

    if hasattr(value, "default"):
        fr_field_parameters["example"] = value.default

where value.default is a marshmallow.missing object. json.dumps does not know how to serialize that object, see traceback output below.

Tested with:

Example

from flask import Flask
from flask_accepts import responds
from flask_restx import Api, Resource
from marshmallow import Schema, fields

api = Api()
ns = api.namespace('main')

class RespondSchema(Schema):
    data = fields.String()

@ns.route('/')
@ns.doc()
class Endpoint(Resource):
    @responds(schema=RespondSchema, api=api)
    def get(self):
        """Doesn't do much"""
        return {'data': 'OK'}

app = Flask('appname', static_folder='</path/to>/Swagger/dist', static_url_path='/swaggerui')
api.init_app(app)
app.run()

(Note: I cloned swagger-ui from their github repo into ~/Swagger)

Error in the browser:

swagger_error

Output from Flask:

127.0.0.1 - - [08/Apr/2021 15:55:21] "GET /swagger.json HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 651, in error_router
    return original_handler(f)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 649, in error_router
    return self.handle_error(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 651, in error_router
    return original_handler(f)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 649, in error_router
    return self.handle_error(e)
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 392, in wrapper
    return self.make_response(data, code, headers=headers)
  File "/usr/local/lib/python3.8/site-packages/flask_restx/api.py", line 415, in make_response
    resp = self.representations[mediatype](data, *args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/flask_restx/representations.py", line 27, in output_json
    dumped = dumps(data, **settings) + "\n"
  File "/usr/local/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/usr/local/lib/python3.8/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/local/lib/python3.8/json/encoder.py", line 431, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/usr/local/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/usr/local/lib/python3.8/json/encoder.py", line 438, in _iterencode
    o = _default(o)
  File "/usr/local/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type _Missing is not JSON serializable
BugliL commented 3 years ago

Same Issue here, the problem is not just related on @responds but @accepts raises the same error.

Wormfriend commented 3 years ago

I described a quick "fix" (not really I guess...) for another issue ( #86 ), which somehow also seems to solve this issue here. I am not sure if this is the correct way to go, any ideas? Please be kind 0.0, it was just a 30 minute hacky thing and I have no idea what else might be related to that.

mingchouliao commented 3 years ago

Bump. also have the same issue here since the last update on marshmallow library.

nicholasprayogo commented 3 years ago

bump, got the same issue as well