2

I have an immutable Pydantic model (Item) that requires a field shape_mode that is schematic or realistic. The default value of the shape_mode should depend on the response class:

  • for application/json default is schematic
  • for image/png default is realistic

The question is: can I modify the field shape_mode based on the accept header, before the body is parsed to an Item object?

from enum import Enum

from fastapi import FastAPI, Request
from pydantic import BaseModel


class ShapeModeEnum(str, Enum):
    schematic = "schematic"
    realistic = "realistic"


class Item(BaseModel):
    name: str
    shape_mode: ShapeModeEnum

    class Config:
        allow_mutation = False
        extra = "forbid"
        strict = True
        validate_assignment = True


app = FastAPI()

RESPONSE_CLASSES = {
    "application/json": "schematic",
    "image/png": "realistic",
}


@app.post("/item")
async def create_item(
    request: Request,
    item: Item,
):
    accept = request.headers["accept"]
    return [item, accept]
Joost Döbken
  • 3,450
  • 2
  • 35
  • 79
  • You could use a middleware to modify the body as in [this answer](https://stackoverflow.com/a/73464007/17865804). – Chris Oct 21 '22 at 15:38
  • I don't understand what you are trying to achieve. If you require an `Item` in a request body (in your example, `shape_mode` is a required field), why do you want to mutate it later on when FastAPI is parsing it? – JarroVGIT Oct 21 '22 at 15:57

1 Answers1

2

You could do this by combining a couple of concepts; inheriting BaseModels and dependencies. Below is a working example (although not really robust; it will throw HTTP500 if you try to post a request with a non-valid accept header.

from enum import Enum

from fastapi import Depends, FastAPI, Request
from pydantic import BaseModel

app = FastAPI()


class ShapeModeEnum(str, Enum):
    schematic = "schematic"
    realistic = "realistic"


class ItemBase(BaseModel):
    name: str


class Item(ItemBase):
    shape_mode: ShapeModeEnum

    class Config:
        # allow_mutation = False
        extra = "forbid"
        strict = True
        validate_assignment = True


def get_real_item(request: Request, item: ItemBase) -> Item:
    RESPONSE_CLASSES = {"application/json": "schematic", "image/png": "realistic"}

    return Item(
        name=item.name, shape_mode=RESPONSE_CLASSES[request.headers.get("Accept", None)]
    )


@app.post("/item")
async def get_item(item: Item = Depends(get_real_item)):
    return item


@app.get("/")
async def root():
    return {"hello": "world"}


if __name__ == "__main__":
    import uvicorn

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

Calling with curl:

% curl -X 'POST' \
  'http://localhost:8000/item' \
  -H 'accept: image/png' \ 
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string"
}'
{"name":"string","shape_mode":"realistic"}%

% curl -X 'POST' \
  'http://localhost:8000/item' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string"
}'
{"name":"string","shape_mode":"schematic"}%   
JarroVGIT
  • 4,291
  • 1
  • 17
  • 29