jmcarp / flask-apispec

MIT License
655 stars 156 forks source link

Possible to decorate with marshal_with many=True and many=False ? #211

Closed Anti-Distinctlyminty closed 3 years ago

Anti-Distinctlyminty commented 3 years ago

I have endpoints that are capably of returning a single item, or a list of many, depending on the query paramters. However I cannot seem to use the marshal_with to properly annotate that. What I want to do is soemthing like

class ClientResource(flask_apispec.views.MethodResource):

    @flask_apispec.marshal_with(ClientOutputSchema(many=True), code=200)
    @flask_apispec.marshal_with(ClientOutputSchema(many=False), code=200)
    def get(self, **kwargs):
        if 'id' in kwargs:
            many=True
            query = ...

        return query.all() if many else query.first()

However this only uses the outermost decorator (many=True in this case). Is this type if response marshaling supported?

g0di commented 3 years ago

According to OpenAPI specification, the only way to document a route which can return different schemas is by using oneOf keyword to indicate that the response may be a list or a single item.

You could use marshmallow-oneofschema library to be able to create a "super" schema which is a oneOf of a single or list schemas and combine with apispec-oneofschema to generate the openapi documentation accordingly

However, after trying to do it myself and looking at the library you have no way to create a OneOfSchema which could do what you want (doing a oneof between a single schema and a list of schema).

Following snippet of code may help you

from marshmallow import Schema
from marshmallow.fields import String
from marshmallow_oneofschema import OneOfSchema

class Item(Schema):
    foo = String()

class ItemOrList(OneOfSchema):
    type_schemas = {"single": Item, "list": Item(many=True)}

    def get_obj_type(self, obj):
        if isinstance(obj, list):
            return "list"
        else:
            return "single"

ItemOrList().dump([{"foo": "ok"}, {"foo": "ok"}])
# This is not doing what you expect
# You got: {"type": "list"}
Anti-Distinctlyminty commented 3 years ago

Thank you for the snippet @hiboo. I have since separated out endpoints into single and multiple responses. Having one endpoint able to return different structures never sat right with me anyway, so although it was quite a bit of work, I went ahead and rebuilt everything using flask-smorest.