question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

422: "value is not a valid dict" on Form() BaseModel validation - @app.post uses Path(), File() and Form() - Request called from SwaggerUI

See original GitHub issue

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,
    File,
    UploadFile,
    Form,
)
from pydantic import BaseModel, Field

app = FastAPI()


# Models
class ModelProcessor(str, Enum):
    CPU = "cpu"
    GPU = "gpu"
    TPU = "tpu"
    DEFAULT = CPU


class ModelThresholds(BaseModel):
    confidence: float = Field(0.5, ge=0.0, le=1.0, descritpiton="Confidence Threshold")
    nms: float = Field(
        0.4, ge=0.0, le=1.0, description="Non-Maximum Suppression Threshold"
    )


class ModelOptions(BaseModel):
    processor: ModelProcessor = Field(None, description="Processor to use for model")
    height: int = Field(
        416, ge=1, description="Height of the input image (resized for model)"
    )
    width: int = Field(
        416, ge=1, description="Width of the input image (resized for model)"
    )
    square: bool = Field(False, description="Zero pad the image to be a square")
    fp_16: bool = Field(False, description="model uses Floating Point 16 Backend (EXPERIMENTAL!)")

    thresholds: ModelThresholds = Field(
        default_factory=ModelThresholds, description="Thresholds for the model"
    )


@app.post("/detect/{model_uuid}", summary="Run detection on an image")
async def object_detection(
        model_uuid: str,
        model_options: ModelOptions = Form(),
        image: UploadFile = File(..., description="Image to run the ML model on"),
):
    print(f"Running detection on image '{image.filename}'")
    return {"filename": image.filename}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=5001)

Description

What I am doing

  • Open browser and head to http://localhost:5001/docs to open the SwaggerUI
  • Try out the only defined endpoint, you can enter any string into model_uuid field and any image for the image: UploadFile, the model_options is prefilled with proper JSON based upon the BaseModel.
  • Hit execute, a ‘422: Unprocessable Entity’ error is returned.

What is happening

Error returned is 422: Unprocessable Entity ->

{
  "detail": [
    {
      "loc": [
        "body",
        "model_options"
      ],
      "msg": "value is not a valid dict",
      "type": "type_error.dict"
    }
  ]
}

What I expect to happen

I expect that the return statement is returned with a 200 response code.

Operating System

Linux

Operating System Details

No response

FastAPI Version

0.79.0

Python Version

3.10.6

Additional Context

Screenshot_2022-09-05_09-42-25 Screenshot_2022-09-05_09-42-33

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:2
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
iudeencommented, Sep 5, 2022

The only limitation here is you will not be able to have nested JSONs within form.

from enum import Enum
from typing import Type
import inspect

from fastapi import (
    FastAPI,
    File,
    UploadFile,
    Form, Depends,
)
from pydantic import BaseModel, Field

app = FastAPI()

def as_form(cls: Type[BaseModel]):
    new_parameters = []

    for field_name, model_field in cls.__fields__.items():
        model_field: ModelField  # type: ignore

        new_parameters.append(
             inspect.Parameter(
                 model_field.alias,
                 inspect.Parameter.POSITIONAL_ONLY,
                 default=Form(...) if not model_field.required else Form(model_field.default),
                 annotation=model_field.outer_type_,
             )
         )

    async def as_form_func(**data):
        return cls(**data)

    sig = inspect.signature(as_form_func)
    sig = sig.replace(parameters=new_parameters)
    as_form_func.__signature__ = sig  # type: ignore
    setattr(cls, 'as_form', as_form_func)
    return cls

# Models
class ModelProcessor(str, Enum):
    CPU = "cpu"
    GPU = "gpu"
    TPU = "tpu"
    DEFAULT = CPU


class ModelThresholds(BaseModel):
    confidence: float = Field(0.5, ge=0.0, le=1.0, descritpiton="Confidence Threshold")
    nms: float = Field(
        0.4, ge=0.0, le=1.0, description="Non-Maximum Suppression Threshold"
    )

class ModelOptions(BaseModel):
    processor: ModelProcessor = Form(None, description="Processor to use for model")
    height: int = Form(
        416, ge=1, description="Height of the input image (resized for model)"
    )
    width: int = Form(
        416, ge=1, description="Width of the input image (resized for model)"
    )
    square: bool = Form(False, description="Zero pad the image to be a square")
    fp_16: bool = Form(False, description="model uses Floating Point 16 Backend (EXPERIMENTAL!)")

    # thresholds: ModelThresholds = Form(...)



@app.post("/detect/{model_uuid}", summary="Run detection on an image")
async def object_detection(
        model_uuid: str,
        model_options: ModelOptions = Depends(),
        image: UploadFile = File(..., description="Image to run the ML model on"),
):
    print(f"Running detection on image '{image.filename}'")
    return {"filename": image.filename}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=5001)

In the above code, I just commented out thresholds and changed how you have formed the BaseModel

2reactions
iudeencommented, Sep 5, 2022

@baudneo one last attempt to get as close as you want this to be. This handles everything.

import json
from enum import Enum
from typing import Type, Dict, Optional
import inspect

from fastapi import (
    FastAPI,
    File,
    UploadFile,
    Form, Depends,
)

from dataclasses import dataclass, field

from pydantic import BaseModel

app = FastAPI()


# Models
class ModelProcessor(str, Enum):
    CPU = "cpu"
    GPU = "gpu"
    TPU = "tpu"
    DEFAULT = CPU

class ModelThresholds(BaseModel):
    confidence: float = Form(0.5, ge=0.0, le=1.0, descritpiton="Confidence Threshold")
    nms: float = Form(
        0.4, ge=0.0, le=1.0, description="Non-Maximum Suppression Threshold"
    )

@dataclass
class ModelOptions:
    processor: ModelProcessor = Form(None, description="Processor to use for model")
    height: int = Form(
        416, ge=1, description="Height of the input image (resized for model)"
    )
    width: int = Form(
        416, ge=1, description="Width of the input image (resized for model)"
    )
    square: bool = Form(False, description="Zero pad the image to be a square")
    fp_16: bool = Form(False, description="model uses Floating Point 16 Backend (EXPERIMENTAL!)")

    thresholds: Optional[str] = Form(ModelThresholds().json())


@app.post("/detect/{model_uuid}", summary="Run detection on an image")
async def object_detection(
        model_uuid: str,
        model_options: ModelOptions = Depends(),
        image: UploadFile = File(..., description="Image to run the ML model on"),
):
    threshold = ModelThresholds(**json.loads(model_options.thresholds))
    print(threshold.dict())
    print(f"Running detection on image '{image.filename}'")
    return {"filename": image.filename}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=5001)
Read more comments on GitHub >

github_iconTop Results From Across the Web

FastAPI 0.65.2 POST request fails with "value is not a valid dict ...
Using FastAPI 0.65.2 , a POST request via the requests module ... BaseModel import uvicorn app = FastAPI() class Data(BaseModel): field: str ...
Read more >
Python: FastAPI error 422 with POST request when sending ...
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Data(BaseModel): user: str @app.post("/") def main(data: ...
Read more >
Declare Request Example Data - FastAPI
Declare Request Example Data¶. You can declare examples of the data your app can receive. Here are several ways to do it.
Read more >
Usage with FastAPI - ODMantic
If the request body doesn't match our model schema, a 422 Unprocessable Entity error will be returned by the API, containing the details...
Read more >
tiangolo/fastapi - Gitter
import sys, time import requests def timed_requests(path): start = time.time() result = requests.get(path).json() print(f"completed in {time.time() - start} ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found