flask_accepts icon indicating copy to clipboard operation
flask_accepts copied to clipboard

Nested schemas with many=true do not generate a list of schemas in the swagger docs

Open rogerfachini opened this issue 4 years ago • 6 comments

Hi, thanks for taking the time to make this!

I found a corner case where the generated swagger docs / model do not match the Marshmallow schema provided. For nested fields, the parameter many=True seems to be ignored when creating the model. For example, this schema:

class WidgetSchema(Schema):
    bar = fields.String()

class GadgetSchema(Schema):
    foo = fields.String()
    widgets = fields.Nested(WidgetSchema, many=True)

appears in the generated swagger docs as:

{
  "foo": "string",
  "widgets": {
    "bar": "string"
  }
}

whereas the model should be:

{
  "widgets": [
    {
      "bar": "string"
    }
  ],
  "foo": "string"
}
Here's a minimal reproduction of the problem based on the example in the readme...

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

@dataclass
class Widget:
    bar: str

@dataclass
class Gadget:
    foo: str
    widgets: list

class WidgetSchema(Schema):
    bar = fields.String()

class GadgetSchema(Schema):
    foo = fields.String()
    widgets = fields.Nested(WidgetSchema, many=True)

def create_app():
    app = Flask(__name__)
    api = Api(app)

    @api.route("/gadget")
    class GadgetResource(Resource):
        @responds(schema=GadgetSchema, api=api)
        def get(self):
            return Gadget(foo=None, widgets=[{'bar': None}, {'bar': None}])

    return app


if __name__ == "__main__":
    create_app().run()

rogerfachini avatar Jul 16 '20 22:07 rogerfachini

Can confirm. We have this issue as well.

exit99 avatar Oct 07 '20 18:10 exit99

Hi, thank you for creating this neat library. I also have the same problem. Is there some kind of fix available by now? Enjoy your day!

Wormfriend avatar Apr 28 '21 08:04 Wormfriend

I tried to overcome this by using fields.List but then the swagger documentation fails to build. I narrowed it down to

 'test': {
    'items': {
       'example': <marshmallow.missing>,
        'type': 'number'
    }

where marshmallow.utils._Missing seems to end up in the dict before the json.dumps. Any idea where this is coming from? Seems to be more of an flask-restx issue, doesn't it?

Wormfriend avatar Apr 28 '21 09:04 Wormfriend

It seems kinda hacky but I could resolve the fields.List issue by adding a type-check for ma.utils._Missing within the _ma_field_to_fr_field function. Any ideas about this? Where is this coming from?

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

    # add type-checking for ma.utils._Missing
    if hasattr(value, "default") and not isinstance(value.default, ma.utils._Missing): 
        fr_field_parameters["example"] = value.default

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

    if hasattr(value, "metadata") and "description" in value.metadata:
        fr_field_parameters["description"] = value.metadata["description"]

    if hasattr(value, "missing") and type(value.missing) != ma.utils._Missing:
        fr_field_parameters["default"] = value.missing

    return fr_field_parameters

I have the feeling this is somehow related to issue #103 .

Wormfriend avatar Apr 28 '21 09:04 Wormfriend

Apologies for delays responding here. I would happily review/merge a PR (w/ tests). I unfortunately cannot commit to a reasonable timeframe for addressing this myself due to personal time conflicts.

apryor6 avatar May 04 '21 18:05 apryor6

Thank's for responding. I'll see what I can do. :)

Wormfriend avatar May 05 '21 11:05 Wormfriend