Describe the bug
Fails to autogenerate docs.
To Reproduce
Expected behavior
Generate openapi.json and open autogenerated docs without problems.
Environment:
Additional context
My models look like this:
class PaginatedItems(Generic[Item], BaseModel, abc.ABC):
items: List[Any]
has_after: bool = False
has_before: bool = False
class OrderUser(BaseModel):
name: Optional[str] = None
email: EmailStr
class Order(BaseModel):
order_id: UUID
amount: Decimal
currency: str
# Users part
merchants: List[OrderUser]
handlers: List[OrderUser]
class PaginatedOrders(PaginatedItems[Order]):
items: List[Order] = list()
My route definition looks like that:
@router.get(orders_uri,
response_model=PaginatedOrders
async def list():
...
This results in the following exception when trying to open autogenerated docs:
pydantic.error_wrappers.ValidationError: 4 validation errors
schemas -> OrderUser
value is not a valid dict (type=type_error.dict)
schemas -> OrderUser
value is not a valid dict (type=type_error.dict)
components -> schemas
value is not none (type=type_error.none.allowed)
components
value is not none (type=type_error.none.allowed)
When I comment out the response_model=PaginatedOrders part from route definition everything works, but docs obviously miss response type.
I believe this was very recently fixed in the most recent version of pydantic (not yet supported by FastAPI, if I recall correctly). I'm currently using the following as a workaround:
# my_fast_api.py
from typing import Any, Dict, Sequence, Set, Type
from fastapi import FastAPI, routing
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.constants import REF_PREFIX
from fastapi.openapi.models import OpenAPI
from fastapi.openapi.utils import get_openapi_path
from fastapi.utils import get_flat_models_from_routes
from pydantic import BaseModel
from pydantic.schema import get_model_name_map, model_process_schema
from starlette.routing import BaseRoute
def get_model_definitions(
*, flat_models: Set[Type[BaseModel]], model_name_map: Dict[Type[BaseModel], str]
) -> Dict[str, Any]:
definitions: Dict[str, Dict[Any, Any]] = {}
for model in flat_models:
m_schema, m_definitions = model_process_schema(model, model_name_map=model_name_map, ref_prefix=REF_PREFIX)
# definitions.update(m_definitions)
model_name = model_name_map[model]
definitions[model_name] = m_schema
return definitions
def get_openapi(
*,
title: str,
version: str,
openapi_version: str = "3.0.2",
description: str = None,
routes: Sequence[BaseRoute],
openapi_prefix: str = "",
) -> Dict[str, Any]:
info = {"title": title, "version": version}
if description:
info["description"] = description
output = {"openapi": openapi_version, "info": info}
components: Dict[str, Dict[str, Any]] = {}
paths: Dict[str, Dict[str, Any]] = {}
flat_models = get_flat_models_from_routes(routes)
model_name_map = get_model_name_map(flat_models)
definitions = get_model_definitions(flat_models=flat_models, model_name_map=model_name_map)
for route in routes:
if isinstance(route, routing.APIRoute):
result = get_openapi_path(route=route, model_name_map=model_name_map)
if result:
path, security_schemes, path_definitions = result
if path:
paths.setdefault(openapi_prefix + route.path_format, {}).update(path)
if security_schemes:
components.setdefault("securitySchemes", {}).update(security_schemes)
if path_definitions:
definitions.update(path_definitions)
if definitions:
components.setdefault("schemas", {}).update(definitions)
if components:
output["components"] = components
output["paths"] = paths
openapi = OpenAPI(**output)
return jsonable_encoder(openapi, by_alias=True, include_none=False)
class MyFastAPI(FastAPI):
def openapi(self) -> Dict[str, Any]:
if not self.openapi_schema:
self.openapi_schema = get_openapi(
title=self.title,
version=self.version,
openapi_version=self.openapi_version,
description=self.description,
routes=self.routes,
openapi_prefix=self.openapi_prefix,
)
return self.openapi_schema
I then import MyFastAPI as FastAPI; I'll swap it out once this is fixed again.
(Basically the only change is the commented line in get_model_definitions which is currently overwriting previously computed schemas with None in some situations involving nested models.)
I am not 100% sure that this handles all edge cases properly, but it generated everything correctly in my api (many models of varied shape and nesting).
Thanks for the help @dmontagu!
The latest Pydantic is now included/supported in the latest FastAPI 0.33.0.
Can you update and check?
@tiangolo yes, I’m happy to say I’ve removed the above workaround from my projects. Thanks!
I can confirm it now works fine! Thanks for fixing it so quickly!
@tiangolo I am facing a similar issue,
my code is currently like this -
class SimilarProducts(BaseModel):
count: int
productIds: List[str]
@router.get("/api/v1/similar/products/{user_id}/{product_id}")
async def get_similar_products(
user_id: str = Path(..., title="userid"),
product_id: str = Path(..., title="productid"),
response_model=SimilarProducts,
):
try:
dummy_result = {
"count": 1,
"productIds": [product_id]
}
return dummy_result
except Exception as e:
raise HTTPException(status_code=202, detail="Exception: {}".format(e))
When trying to open /docs I get this error -
TypeError: Object of type 'MetaModel' is not JSON serializable
I'm using fastapi version 0.33.0
pydantic version 0.30
what is MetaModel
On Tue, Jul 23, 2019 at 10:42 AM Dhruv Karan notifications@github.com
wrote:
@tiangolo https://github.com/tiangolo I am facing a similar issue,
my code is currently like this -
class SimilarProducts(BaseModel):
count: int
productIds: List[str]@router.get("/api/v1/similar/products/{user_id}/{product_id}")
async def get_similar_products(
user_id: str = Path(..., title="userid"),
product_id: str = Path(..., title="productid"),
response_model=SimilarProducts,
):
try:
dummy_result = {
"count": 1,
"productIds": [product_id]
}
return dummy_result
except Exception as e:
raise HTTPException(status_code=202, detail="Exception: {}".format(e))When trying to open /docs I get this error -
TypeError: Object of type 'MetaModel' is not JSON serializable
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/tiangolo/fastapi/issues/383?email_source=notifications&email_token=AAINSPRBJXVZV76AGEKOBU3QA276LA5CNFSM4ICBLKDKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2SMCDI#issuecomment-514113805,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAINSPXWMX55R3KMDZTTURDQA276LANCNFSM4ICBLKDA
.
--
benoit barthelet
http://pgp.mit.edu/pks/lookup?op=get&search=0xF150E01A72F6D2EE
@euri10 it's not there in my code, I thought it was something inside Pydantic?
oups sorry I think your mistake is putting response_model=SimilarProducts, in the wrong spot, it's in the @router part
oh yes, it works, my bad.
thanks! @euri10
Thanks @dmontagu and @LKay for reporting back (and closing the issue).
Thanks @euri10 for your help! I'm glad it works now @unography.
Most helpful comment
oups sorry I think your mistake is putting
response_model=SimilarProducts,in the wrong spot, it's in the @router part