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?
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"}]}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With