Fastapi: [BUG] Generic pydantic model causes inconsistent OpenAPI spec generation

Created on 25 Oct 2019  路  5Comments  路  Source: tiangolo/fastapi

Describe the bug
I am wrapping my responses in a generic model like below (full code in toggle section).

class Response(GenericModel, Generic[DataT]):
    """Wrapper for responses"""
    data: Optional[DataT]

I see two issues:

  • The /docs endpoint renders things differently for a contained list or a contained single element.
  • PyCharm warns on type mismatch according to the annotations

I am wondering if I am using the GenericModel in a wrong way or if there is a bug?


Click to toggle

from typing import TypeVar, Generic, Optional, List

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.generics import GenericModel

DataT = TypeVar('DataT')


class Response(GenericModel, Generic[DataT]):
    """Wrapper for responses"""
    data: Optional[DataT]


class ProjectOut(BaseModel):
    """Project model (out)"""
    id: int


app = FastAPI()

out = ProjectOut(id=1)


@app.get(
    '/projects/{item_id}',
    response_model=Response[ProjectOut]
)
def get_single() -> Response[ProjectOut]:
    return Response[ProjectOut](data=out)


@app.get(
    '/projects',
    response_model=Response[List[ProjectOut]])
def get_projects() -> Response[List[ProjectOut]]:
    return Response[List[ProjectOut]](data=[out])


if __name__ == '__main__':
    uvicorn.run(
        f"{__name__}:app",
        host='0.0.0.0',
        port=8888,
        reload=True,
        debug=True,
        log_level='info')

To Reproduce

Expected behavior

  • The list response is shown as Response[List[ProjectOut]]
  • I see no warnings in PyCharm when I am using annotations correctly.

Screenshots

Screenshot 2019-10-25 08 34 07
Screenshot 2019-10-25 08 33 07

Environment:

  • OS: macOS
  • FastAPI Version: 0.42

    • Python version: 3.7.4

Additional context
Add any other context about the problem here.

bug

All 5 comments

As of pydantic v1, there is support for overriding the naming behavior of a generic class; see bottom of the section on generic models in the docs https://pydantic-docs.helpmanual.io/usage/models/#generic-models.

In the meantime until we have pydantic v1 support, your best bet is probably:

ResponseListProjectOut = Response[List[ProjectOut]]
ResponseListProjectOut.__name__ = "Response[List[ProjectOut]]"  # or whatever

...

@app.get('/projects', response_model=ResponseListProjectOut)
def get_projects() -> ResponseListProjectOut:
    return ResponseListProjectOut(data=[out])

I'm working on a "fix" to the type thing now. PyCharm doesn't make it easy though since it is basically impossible to hide method definitions from it. But I think annotating __class_getitem__ to return Type[Any] fixes it.

Also have in mind that your path operation function might return something different, like a DB model, a list of dicts, etc. And it will still be validated/parsed/converted using the type declared in response_model.

In those cases, you would have to declare the return type of your function as such for mypy to like it, even though your API is actually returning something else.

Also, you're probably never calling your path operation function directly, FastAPI calls it for you, so you will probably never get a lot of benefit from annotating it's return type.

My suggestion is, annotate everything else for your own sanity, but the path operation function... meh, no benefit from that.

I am quite new to the annotations thing and did think through the implications. Thanks for the hint to skip annotations for the path operations.

Sure! :smile:

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

Was this page helpful?
0 / 5 - 0 ratings