Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

OpenAPI KeyError on body parameter when field types resolve to the same type #4191

Closed
9 tasks done
mayteio opened this issue Nov 19, 2021 · 8 comments
Closed
9 tasks done
Labels
question Question or problem question-migrate

Comments

@mayteio
Copy link

mayteio commented Nov 19, 2021

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

from enum import Enum
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Generic, Optional, TypeVar

DataT = TypeVar("DataT")


class Type(str, Enum):
  green = "green"
  blue = "blue"


class State(str, Enum):
  published = "published"
  archived = "archived"


class FieldFilter(GenericModel, Generic[DataT]):
  eq: Optional[DataT]
  neq: Optional[DataT]

class StringFilter(FieldFilter[str]):
  ilike: Optional[str]
  nilike: Optional[str]

class Where(BaseModel):
  name: Optional[StringFilter]
  # changing either of these lines to FieldFilter[str] resolves the error
  # but obviously we'd like the enums to display in openapi.json rather than str
  type: Optional[FieldFilter[Type]]
  state: Optional[FieldFilter[State]]


class QueryParams(BaseModel):
  where: Optional[Where]
  limit: Optional[int]
  offset: Optional[int]


app = FastAPI()

@app.post("")
def search(params: QueryParams):
    return {"Hello":"Tiangolo"}

Description

  • Open the browser and hit /openapi.json
  • You see Internal Server Error when you should see the openapi.json
INFO:     172.31.0.1:62102 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 398, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/usr/local/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/usr/local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.9/site-packages/starlette/middleware/cors.py", line 78, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/ddtrace/contrib/asgi/middleware.py", line 178, in __call__
    reraise(exc_type, exc_val, exc_tb)
  File "/usr/local/lib/python3.9/site-packages/six.py", line 719, in reraise
    raise value
  File "/usr/local/lib/python3.9/site-packages/ddtrace/contrib/asgi/middleware.py", line 173, in __call__
    return await self.app(scope, receive, wrapped_send)
  File "/usr/local/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/usr/local/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.9/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/usr/local/lib/python3.9/site-packages/fastapi/applications.py", line 152, in openapi
    return JSONResponse(self.openapi())
  File "/usr/local/lib/python3.9/site-packages/fastapi/applications.py", line 130, in openapi
    self.openapi_schema = get_openapi(
  File "/usr/local/lib/python3.9/site-packages/fastapi/openapi/utils.py", line 354, in get_openapi
    definitions = get_model_definitions(
  File "/usr/local/lib/python3.9/site-packages/fastapi/utils.py", line 28, in get_model_definitions
    model_name = model_name_map[model]
KeyError: <class 'app.gateways.rest.schemas.projects.FieldFilter[str]'>

Operating System

macOS

Operating System Details

No response

FastAPI Version

0.63.0

Python Version

3.9.7

Additional Context

Possibly related, but slightly different issues:

Also, removing the str inheritance in either Type or State resolves the issue, though messes with the rest of our application logic.

@mayteio mayteio added the question Question or problem label Nov 19, 2021
@mayteio mayteio changed the title [BUG] OpenAPI KeyError on response_model when field types resolve to the same type [BUG] OpenAPI KeyError on body parameter when field types resolve to the same type Nov 19, 2021
@jfitz1
Copy link

jfitz1 commented Jan 6, 2022

+1

@xeroc
Copy link

xeroc commented May 9, 2022

+1

I am using Response[List[xx]] on many occasions. Getting a KeyError.

//edit: interestingly, this issue disappeared when downgraded from python3.10 to python3.8.

@antoniodipinto
Copy link

+1

@JarroVGIT
Copy link
Contributor

@antoniodipinto I can no longer reproduce this on python 3.10 and FastAPI 0.79. Which versions are you using?

@antoniodipinto
Copy link

Hi @JarroVGIT, seems that I have this issue on Python version 3.8 and FastAPI 0.79

@JarroVGIT
Copy link
Contributor

JarroVGIT commented Jul 24, 2022

Weird. I just tried it with python 3.8.13 and FastAPI 0.79, and still couldn't reproduce. I took the code example from the issue opening post and ran it. When requesting /openapi.json, I am seeing a normal JSON and not this. You sure you have the same issue? I know there are similar issues like this one (also with KeyError) when using dataclasses rather than BaseModels. See this issue #5138

@antoniodipinto
Copy link

Update: everything is working fine right now. Probably some local python error. Reconfigured the venv and problem solved
Thaks @JarroVGIT for the help

cercide added a commit to cercide/fastapi-xml that referenced this issue Aug 18, 2022
- preventing fastapg issue [#4191](tiangolo/fastapi#4191)
- readme update
@cercide
Copy link

cercide commented Aug 18, 2022

Error Reason:
fastapi converts a dataclasses.dataclass into class pydantic.BaseModel model. Thereby, fastapi treats the same dataclass model as a different class whenever a dataclass referenced mutliple times p.e. as response_model and at different endpoints (this behaviour is probably not intendet).

First, APIRoute constructor ćonverts the dataclasses as mentioned bevore. In this example, fastapi.utils.create_response_field called at fastapi.routing.APIRoute line 389 converts the response model for each route. create_response_field is called twice since both endpoints have the same response model. Hence, both endpoints do not have the exact same response model.

Next, the openapi schema is generated using fastapi.openapi.utils.get_openapi. This function calls pydantic.schema.get_model_name_map at line 413. get_model_name_map expects a set of unique models. In this case however, fastapi passes two different classes since id(route_one.response_model) != id(route_two.response_model), which share the same class name (and module name). Consequently, get_model_name_map removes one class from its result.

Finally, this ends up in a KeyError in get_model_definitions.

What to do until this bug is fixed?
Basically there are three options:

  • Do not use the same model twice or avoid the openapi schema.
  • The bug occurs with pydantic v1.9.2 (current release) and before. However, pydantic's master branch has a rewritten version of pydantic.dataclasses that solves this issue.
  • Patch the errors during runtime. For instance, fastapi-xml uses a modified version of pydantic.dataclasses._process_class that fixex the issue. That patch solves also the pydantic issue #4353

cercide added a commit to cercide/fastapi-xml that referenced this issue Aug 18, 2022
- covering further side effects of [#4191](tiangolo/fastapi#4191)
- FASTAPI_XML_DISABLE_PYDANTIC_PATCH disables the pydantic patch
- added pydantic license
- readme update
@tiangolo tiangolo changed the title [BUG] OpenAPI KeyError on body parameter when field types resolve to the same type OpenAPI KeyError on body parameter when field types resolve to the same type Feb 24, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8681 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

7 participants