如何使用 Python 的枚举和 FastAPI 做出不区分大小写的选择?

How to make case insensitive choices using Python's enum and FastAPI?

提问人:Amin Ba 提问时间:4/28/2023 最后编辑:ChrisAmin Ba 更新时间:8/11/2023 访问量:2124

问:

我有这个应用程序:

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或返回“Hello World”curl http://127.0.0.1:8001/?q=cd

但这些中的任何一个

  • 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

返回,这是有道理的。422Unprocessable Entity

如何使此验证不区分大小写?

枚举 fastapi pydantic

评论


答:

7赞 Chris 4/29/2023 #1

您可以通过重写 's _missing_ 方法使值不区分大小写。根据文档,这个类方法(默认情况下不执行任何操作)可用于查找 ;因此,允许人们尝试通过 找到枚举成员。enumEnumclsvalue

请注意,在声明枚举类时,可以从类扩展(例如,),这将指示枚举中的所有成员都必须具有指定类型的值(例如,)。这也允许将字符串与枚举成员进行比较(使用相等运算符),而不必在枚举成员上使用属性(例如, )。否则,如果枚举类被声明为(没有子类),则需要使用枚举成员的属性(例如,)来安全地将枚举成员与字符串进行比较。strclass MyEnum(str, Enum)str==valueif member.lower() == valueclass MyEnum(Enum)strvalueif member.value.lower() == value

另请注意,除非类的枚举值也包含大写(或大写和小写字母的组合),否则不需要在枚举成员上调用该函数。因此,对于下面的示例,如果只使用小写字母,您可以避免使用它,而只是用于将枚举成员与值进行比较;因此,您可以避免对类中的每个成员调用函数。lower()if member == valuelower()

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

示例(在 Python 3.11+ 中)

Python 3.11+ 中,可以改用新引入的 StrEnum,它允许使用 auto() 功能,从而将成员名称的小写版本作为值。

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

评论

0赞 Amin Ba 4/29/2023
伟大。它有效。你的答案有一个小问题。如果原始 vlaues 是大写或不是小写的,则不起作用。也许你应该把它改成def _missing_(cls, value): for member in cls: if member.value.lower() == value.lower(): return member
1赞 Chris 4/29/2023
没有使用 ,因为问题中发布的原始代码使用小写字母表示成员值,并且人们很容易弄清楚并根据自己的大小写进行调整。但是,无论如何,感谢您指出它 - 它现在已经改变了。另外,我不建议使用 ,如您上面的评论所示,而是将值转换为 之外的小写,否则,您将不必要地为循环中的每个枚举成员调用函数。.lower() member.value== value.lower()for loop.lower()
0赞 Amin Ba 4/29/2023
另一个建议。将类更改为MyEnum(str, enum.Enum)
1赞 Chris 4/29/2023
它已更改为 ,但并非必须这样做,因为上面的示例使用每个枚举成员的属性将值与字符串进行比较。但是,通过这样做,您也可以改用。因此,在比较端点内部的值时,它也可能很方便,因为您不必使用该属性。此外,如果类的任何枚举成员包含非类型值(例如,数字、日期),它们将自动转换为 。(str, Enum)valueif member.lower() == valuevalueMyEnumstrstr
0赞 Amin Ba 4/29/2023
如果你是从 str 继承的,你不应该做 .你应该做的member.value.lower()member.lower()
0赞 Hunter_71 8/11/2023 #2

我真的很喜欢公认的答案的建议,但是它可以简化一点(和概括):

class CaseInsensitiveEnum(str, Enum):
    @classmethod
    def _missing_(cls, value: str):
        for member in cls:
            if member.lower() == value.lower():
                return member
        return None


class MyEnum(CaseInsensitiveEnum):
    ab = 'ab'
    cd = 'cd'

正如之前的评论中所建议的,它可以通过两种方式进行优化:

  • member.lower()如果所有枚举值都将指定为小写,则不需要
  • value.lower()可以在 for 循环之外执行一次