Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]Can't support list type swagger response #96

Closed
sunlin92 opened this issue Dec 13, 2020 · 20 comments · Fixed by #222
Closed

[Feature]Can't support list type swagger response #96

sunlin92 opened this issue Dec 13, 2020 · 20 comments · Fixed by #222

Comments

@sunlin92
Copy link

like:

class Item(BaseModel):
    name: str
    price: float


@app.route("/", method=["POST"])
@api.validate(json=Item, resp=Response(HTTP_200=List[Item]), tags=["demo"])
def demo():
    item = Item.parse_obj(request.context.json)
    return [item, item, item]
@kemingy
Copy link
Member

kemingy commented Dec 14, 2020

One possible way is to use __root__ pydantic root.

class Item(BaseModel):
    name: str
    price: float


class ItemList(BaseModel):
    __root__: List[Item]


@app.route("/", method=["POST"])
@api.validate(json=Item, resp=Response(HTTP_200=ItemList), tags=["demo"])
def demo():
    item = request.context.json
    return ItemList.parse_obj([item, item, item])

By the way, you don't need to parse request.context.json again. For the return statement, you can use return ItemList.parse_obj([item, item, item]) or return ItemList(__root__=[item, item, item]).

@sunlin92
Copy link
Author

I really appreciate your help. 😄
Are there any plans to support this syntax(HTTP_200=List[Item])?
It looks like be easier to read and use.

Thanks again

@kemingy
Copy link
Member

kemingy commented Dec 14, 2020

This syntax looks nice. But I feel it can be difficult to support. Because it means the library should be able to verify any types:

  • List[Item]
  • Dict[str, Item]
  • Dict[str, List[Item]]
  • ...

I'm not sure if there is an easy way to do this. Let me know if you have any ideas.

@sunlin92
Copy link
Author

FastAPI seems to support this syntax, I will create a pr when i complete it if needed.

@kemingy
Copy link
Member

kemingy commented Dec 14, 2020 via email

@yoursvivek
Copy link
Contributor

Data validation is easy. Pydantic provides a function to load data of any supported type or a combination thereof.

One just needs to handle registry into spec.models.

https://pydantic-docs.helpmanual.io/usage/models/#parsing-data-into-a-specified-type

@onecrayon
Copy link
Contributor

Has anyone undertaken this issue yet? It’s something I need and I was wondering if I should take a swing at it or if it’s already underway.

@kemingy
Copy link
Member

kemingy commented Jul 4, 2021

Has anyone undertaken this issue yet? It’s something I need and I was wondering if I should take a swing at it or if it’s already underway.

This issue has been inactive for a long time. @onecrayon Feel free to create a WIP PR and link to this issue. Thanks for your help.

@alexted
Copy link

alexted commented Apr 29, 2022

Hey, guys! This feature is incredibly necessary! When are you going to do it? I really need it!
@sunlin92 @yoursvivek @kemingy where are you, guys? pls, do it!

@kemingy
Copy link
Member

kemingy commented Apr 30, 2022

Hey, guys! This feature is incredibly necessary! When are you going to do it? I really need it! @sunlin92 @yoursvivek @kemingy where are you, guys? pls, do it!

pydantic.parse_obj_as seems a good solution for the List[BaseModel]. One more thing is how to generate the corresponding OpenAPI doc.

If we narrow it down to support List[BaseModel] only, it's possible to write a function to handle it.

Hi, @onecrayon are you still willing to take this one?

@onecrayon
Copy link
Contributor

Possibly; my app that leverages SpecTree has been lying fallow for a bit here, but I’ve been hoping to get back into it. I’ll let you know if I start actual coding on this, but don’t want to make any solid promises at this point.

@kemingy
Copy link
Member

kemingy commented Apr 30, 2022

After reading the source code of pydantic.main.ModelMetaclass, I found a way to do this:

from typing import List, Type
from flask import Flask, jsonify
from spectree import SpecTree, Response
from pydantic import BaseModel


class User(BaseModel):
    name: str
    page: int


app = Flask(__name__)
spec = SpecTree("flask")


@app.route("/")
@spec.validate(resp=Response(HTTP_200=User))
def index():
    return User(name="John", page=1)


def gen_list_model(model: Type[BaseModel]):
    assert issubclass(model, BaseModel)
    ListModel = type(
        f"{model.__name__}List",
        (BaseModel,),
        {
            "__annotations__": {"__root__": List[model]},
        },
    )
    return ListModel


@app.route("/list")
@spec.validate(resp=Response(HTTP_200=gen_list_model(User)))
def list_user():
    return jsonify([{"name": "John", "page": 1}, {"name": "Jane", "page": 2}])


if __name__ == "__main__":
    spec.register(app)
    app.run()

@alexted
Copy link

alexted commented Apr 30, 2022

@kemingy It looks like some kind of crutches...and looks not 'pythonic'.
Isn't there a way to do it literally like in FastAPI - to make it work by just wrapping the model in List[]?

@kemingy
Copy link
Member

kemingy commented Apr 30, 2022

@kemingy It looks like some kind of crutches...and looks not 'pythonic'. Isn't there a way to do it literally like in FastAPI - to make it work by just wrapping the model in List[]?

That example is just an idea (also the real implementation). Check #222 for the interface.

@alexted
Copy link

alexted commented Apr 30, 2022

An error occurs when I try to run this code:

FLASK_APP = app.py
FLASK_ENV = development
FLASK_DEBUG = 0
In folder /home/alex/PycharmProjects/flask-spectree
/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/bin/python -m flask run
 * Serving Flask app 'app.py' (lazy loading)
 * Environment: development
 * Debug mode: off
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/__main__.py", line 3, in <module>
    main()
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 988, in main
    cli.main()
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 579, in main
    return super().main(*args, **kwargs)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/decorators.py", line 84, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 850, in run_command
    app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 299, in __init__
    self._load_unlocked()
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 333, in _load_unlocked
    self._app = rv = self.loader()
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 389, in load_app
    app = locate_app(import_name, name)
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/flask/cli.py", line 234, in locate_app
    __import__(module_name)
  File "/home/alex/PycharmProjects/flask-spectree/app.py", line 24, in <module>
    @spec.validate(resp=Response(HTTP_200=List[User]))
  File "/home/alex/.cache/pypoetry/virtualenvs/flask-spectree-64sAPQko-py3.10/lib/python3.10/site-packages/spectree/response.py", line 59, in __init__
    assert issubclass(model, BaseModel), "invalid `pydantic.BaseModel`"
  File "/usr/lib/python3.10/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class

Process finished with exit code 1

@kemingy
Copy link
Member

kemingy commented May 1, 2022

An error occurs when I try to run this code:

Sorry I cannot reproduce this with Python 3.10

@kemingy
Copy link
Member

kemingy commented May 1, 2022

I have created a pre-release: https://github.com/0b01001001/spectree/releases/tag/v0.10.0a1

Can try to use this one to see if it can fulfill your requirements.

@alexted
Copy link

alexted commented May 1, 2022

Just what I needed, thank you!
But i think it would be good to correct the type hints:
Screenshot from 2022-05-08 08-18-32

@alexted
Copy link

alexted commented May 18, 2022

@kemingy what about update type hinting? ^^

@yedpodtrzitko
Copy link
Collaborator

@alexted you know it's an open-source project, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants