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]
2
  • You could use a middleware to modify the body as in this answer. Commented Oct 21, 2022 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? Commented Oct 21, 2022 at 15:57

1 Answer 1

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"}%   
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.