Skip to content

[BUG] Using Nested Pydantic models and params: MyModel = Depends() forces OpenAPI docs GET methods to require a request body. #11037

@TrevorBenson

Description

@TrevorBenson

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.
    • Not familiar 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.
  • After submitting this, I commit to:
    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
    • Or, I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
    • Implement a Pull Request for a confirmed bug.

Example

Here's a self-contained minimal, reproducible, example with my use case:

from fastapi import APIRouter, Depends, FastAPI, Query
from fastapi.responses import JSONResponse
from typing import List, Optional
from pydantic import BaseModel, Field
import uvicorn

class MyModelData1(BaseModel):
    archive: Optional[str] = Field(None, description="Archive name")
    archive_type: Optional[str] = Field(None, description="Archive type")

class MetadataGet(BaseModel):
    id: Optional[str] = Field(None, alias="_id")
    foreign_key: Optional[str] = Field(None, alias="_foreign_key")

class DeepNestedModelGet(BaseModel):
    name: Optional[str]
    version: Optional[str]

class DetailsModelGet(BaseModel):
    some_data: Optional[List[Optional[DeepNestedModelGet]]]
    some_data2: Optional[List[Optional[str]]]

class MyModelData2(BaseModel):
    id: Optional[str] = Field(None, alias="_id")
    details: Optional[DetailsModelGet]
    meta: Optional[MetadataGet]

def get_documents(collection, sort_by, sort_order, page, page_size, **additional_filters):
    return {
        "collection": collection,
    }

app = FastAPI()
router1 = APIRouter(prefix="/data1", tags=["Data1"])
router2 = APIRouter(prefix="/data2", tags=["Data2"])

@router1.get("/", description="Retrieve all documents.")
def get_data1(
    sort_by: str = Query(None, description="Sort by this field"),
    sort_order: str = Query(None, description="Sort order"),
    page: int = Query(1, description="Page number"),
    page_size: int = Query(100, description="Number of documents per page"),
    params: MyModelData1 = Depends(),
):
    document = get_documents(
        "data1",
        sort_by,
        sort_order,
        page,
        page_size,
        **params.model_dump()
    )
    return JSONResponse(content=document)

@router2.get("/")
def get_data2(
    sort_by: str = Query(None, description="Sort by this field"),
    sort_order: str = Query(None, description="Sort order"),
    page: int = Query(1, description="Page number"),
    page_size: int = Query(100, description="Number of documents per page"),
    params: MyModelData2 = Depends(),
):
    """Get all software."""
    document = get_documents(
        "data2",
        sort_by,
        sort_order,
        page,
        page_size,
        **params.model_dump(),
    )
    return JSONResponse(content=document)

app.include_router(router1)
app.include_router(router2)



if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=5000)

Description

  • Open the browser and call the GET endpoint /data1/.
  • No request body is required for the GET method
  • Open the browser and call the GET endpoint /data2/.
  • A request body is required for the GET method and provided
  • If the user does not delete the request body since it is invalid a TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body. is returned.

The solution you would like

The request body is not a required part of the GET /data2/ UI documentation when using an identical Depends() structure so dump_model() method can be used to pass very large models to the documentation as query parameters for the GET method which are optional.

Environment

  • OS: Linux
  • FastAPI Version: 0.109.0
  • Python version: 3.9.18

Additional context

I've gone so far as to take List[str] which became Optional[List[str] in the Get version of the model, to an extreme of Optional[List[Optional[str]]] and List[DetailsModelGet] which was already all optional fields into Optional[List[Optional[DetailsModelGet]]]. Pushing Optional down to the last type and making "deeply optional" fields in case somehow this would resolve the issue. I cannot seem to find any instance of how to get the nested models not to result in a required request body in the docs GET method except to remove the nested (optional) models entirely and use a single-layer Pydantic model.

Originally posted by @stevesuh in #7275

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions