I have this application:
import enum
from typing import Annotated, Literal
import uvicorn
from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel
app = FastAPI()
class MyEnum(enum.Enum):
ab = "ab"
cd = "cd"
class MyInput(BaseModel):
q: Annotated[MyEnum, Query(...)]
@app.get("/")
def test(inp: MyInput = Depends()):
return "Hello world"
def main():
uvicorn.run("run:app", host="0.0.0.0", reload=True, port=8001)
if __name__ == "__main__":
main()
curl http://127.0.0.1:8001/?q=ab
or curl http://127.0.0.1:8001/?q=cd
returns "Hello World"
But any of these
curl http://127.0.0.1:8001/?q=aB
curl http://127.0.0.1:8001/?q=AB
curl http://127.0.0.1:8001/?q=Cd
returns 422Unprocessable Entity
which makes sense.
How can I make this validation case insensitive?
You could make case insensitive enum
values by overriding the Enum
's _missing_
method . As per the documentation, this classmethod—which by default does nothing—can be used to look up for values not found in cls
; thus, allowing one to try and find the enum member by value
.
Note that one could extend from the str
class when declaring the enumeration class (e.g., class MyEnum(str, Enum)
), which would indicate that all members in the enum must have values of the specified type (e.g., str
). This would also allow comparing a string to an enum member (using the equality operator ==
), without having to use the .value
attribute on the enum member (e.g., if member.lower() == value
). Otherwise, if the enumeration class was declared as class MyEnum(Enum)
(without str
subclass), one would need to use the .value
attribute on the enum member (e.g., if member.value.lower() == value
) to safely compare the enum member to a string.
Also, note that calling the lower()
function on the enum member (i.e., member.lower()
) would not be necessary, unless the enum member values of your class include uppercase (or a combination of uppercase and lowercase) letters as well (e.g., ab = 'aB'
, cd = 'Cd'
, etc.). Hence, for the example below, where only lowercase letters are used, you could avoid using it, and instead simply use if member == value
to compare the enum member to a value; thus, saving you from calling the lower()
funciton on every member in the class.
from enum import Enum
class MyEnum(str, Enum):
ab = 'ab'
cd = 'cd'
@classmethod
def _missing_(cls, value):
value = value.lower()
for member in cls:
if member.lower() == value:
return member
return None
from fastapi import FastAPI
from enum import Enum
app = FastAPI()
class CaseInsensitiveEnum(str, Enum):
@classmethod
def _missing_(cls, value):
value = value.lower()
for member in cls:
if member.lower() == value:
return member
return None
class MyEnum(CaseInsensitiveEnum):
ab = 'aB'
cd = 'Cd'
@app.get("/")
def main(q: MyEnum):
return q
In case you needed the Enum
query parameter to be defined using Pydantic's BaseModel
, you could then use the below (see this answer and this answer for more details):
from fastapi import Query, Depends
from pydantic import BaseModel
...
class MyInput(BaseModel):
q: MyEnum = Query(...)
@app.get("/")
def main(inp: MyInput = Depends()):
return inp.q
In both cases, the endpoint could be called as follows:
http://127.0.0.1:8000/?q=ab
http://127.0.0.1:8000/?q=aB
http://127.0.0.1:8000/?q=cD
http://127.0.0.1:8000/?q=CD
...
In Python 3.11+, one could instead use the newly introduced StrEnum
, which allows using the auto()
feature, resulting in the lower-cased version of the member's name as the value.
from enum import StrEnum, auto
class MyEnum(StrEnum):
AB = auto()
CD = auto()
@classmethod
def _missing_(cls, value):
value = value.lower()
for member in cls:
if member == value:
return member
return None