Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pydantic / FastAPI, how to set up a case-insensitive model with suggested values

Using FastAPI I have set up a POST endpoint that takes a command, I want this command to be case insensitive, while still having suggested values (i.e. within the SwaggerUI docs)

For this, I have set up an endpoint with a Command class as a schema for the POST body parameters:

@router.post("/command", status_code=HTTPStatus.ACCEPTED)  # @router is a fully set up APIRouter()
async def control_battery(command: Command):
    result = do_work(command.action)
    return result

For Command I currently have 2 possible versions, which both do not have the full functionality I desire.

from fastapi import HTTPException
from pydantic import BaseModel, field_validator
from typing import Literal

## VERSION 1
class Command(BaseModel):
    action: Literal["jump", "walk", "sleep"]


## VERSION 2
class Command(BaseModel):
    action: str

    @field_validator('action')
    @classmethod
    def validate_command(cls, v: str) -> str:
        """
        Checks if command is valid and converts it to lower.
        """
        if v.lower() not in {'jump', 'walk', 'sleep'}:
            raise HTTPException(status_code=422, detail="Action must be either 'jump', 'walk', or 'sleep'")
        return v.lower()

Version 1 is obviously not case sensitive, but has the correct 'suggested value' behaviour, as below.

Whereas Version 2 has the correct case sensitivity and allows for greater control over the validation, but no longer shares suggested values with users of the schema. e.g., in the image above "jump" would be replaced with "string".

How do I combine the functionality of both of these approaches?

like image 955
Floriancitt Avatar asked Dec 07 '25 07:12

Floriancitt


1 Answers

You can use a validator configured with mode="before" to adjust any values before validation (this was named pre=True in pydantic v1):

Example:

from fastapi import FastAPI, HTTPException
from http import HTTPStatus
from pydantic import BaseModel, field_validator
from typing import Literal


class Command(BaseModel):
    action: Literal["jump", "walk", "sleep"]

    @field_validator('action', mode='before')
    @classmethod
    def validate_command(cls, v: str) -> str:
        return v.lower()


app = FastAPI()

@app.post("/command", status_code=HTTPStatus.ACCEPTED)  # @router is a fully set up APIRouter()
async def control_battery(command: Command):
    return {"action": command.action}

Any case version of jump, walk, sleep will work, while other values will give a 422 unprocessable error:

$ curl -s http://.../command -X POST -d '{"action": "JUMP"}' --header "Content-Type: application/json"
{"action":"jump"}
$ curl -s http://.../command -X POST -d '{"action": "foo"}' --header "Content-Type: application/json"
{"detail":[{"type":"literal_error","loc":["body","action"],"msg":"Input should be 'jump', 'walk' or 'sleep'","input":"foo","ctx":{"expected":"'jump', 'walk' or 'sleep'"},"url":"https://errors.pydantic.dev/2.6/v/literal_error"}]}
like image 98
MatsLindh Avatar answered Dec 08 '25 20:12

MatsLindh