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

Ami*_* Ba 5 enums pydantic fastapi

我有这个应用程序:

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()
Run Code Online (Sandbox Code Playgroud)

curl http://127.0.0.1:8001/?q=abcurl http://127.0.0.1:8001/?q=cd返回“Hello World”

但任何这些

  • 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
  • ETC

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

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

Chr*_*ris 11

您可以enum通过重写Enum\ 的_missing_方法来使值不区分大小写。根据文档,此类方法\xe2\x80\x94 默认情况下不执行任何操作 \xe2\x80\x94 可用于查找 ; 中未找到的值cls。因此,允许人们尝试通过 找到枚举成员value

\n

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

\n

另请注意,没有必要lower()在枚举成员(即 )上调用函数,除非类的枚举成员值也包含大写(或大写和小写的组合)字母(例如,, 等) 。因此,对于下面的示例,仅使用小写字母,您可以避免使用它,而只需使用它来将 枚举成员与值进行比较;因此,您无需致电member.lower()ab = \'aB\'cd = \'Cd\'if member == valuelower()类中每个成员的函数。

\n

实施例1

\n
from enum import Enum\n\nclass MyEnum(str, Enum):\n    ab = \'ab\'\n    cd = \'cd\'\n    \n    @classmethod\n    def _missing_(cls, value):\n        value = value.lower()\n        for member in cls:\n            if member.lower() == value:\n                return member\n        return None\n
Run Code Online (Sandbox Code Playgroud)\n

通用版本(以 FastAPI 为例)

\n
from fastapi import FastAPI\nfrom enum import Enum\n\n\napp = FastAPI()\n\n\nclass CaseInsensitiveEnum(str, Enum):\n    @classmethod\n    def _missing_(cls, value):\n        value = value.lower()\n        for member in cls:\n            if member.lower() == value:\n                return member\n        return None\n        \n\nclass MyEnum(CaseInsensitiveEnum):\n    ab = \'aB\'\n    cd = \'Cd\'\n\n\n@app.get("/")\ndef main(q: MyEnum):\n    return q\n
Run Code Online (Sandbox Code Playgroud)\n

如果您需要Enum使用 Pydantic\'s 定义查询参数BaseModel,则可以使用以下内容(有关更多详细信息,请参阅此答案此答案):

\n
from fastapi import Query, Depends\nfrom pydantic import BaseModel\n\n\nclass MyInput(BaseModel):\n    q: MyEnum = Query(...)\n\n\n@app.get("/")\ndef main(inp: MyInput = Depends()):\n    return inp.q\n
Run Code Online (Sandbox Code Playgroud)\n

在这两种情况下,可以按如下方式调用端点:

\n
http://127.0.0.1:8000/?q=ab\nhttp://127.0.0.1:8000/?q=aB\nhttp://127.0.0.1:8000/?q=cD\nhttp://127.0.0.1:8000/?q=CD\n...\n
Run Code Online (Sandbox Code Playgroud)\n

实施例2

\n

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

\n
http://127.0.0.1:8000/?q=ab\nhttp://127.0.0.1:8000/?q=aB\nhttp://127.0.0.1:8000/?q=cD\nhttp://127.0.0.1:8000/?q=CD\n...\n
Run Code Online (Sandbox Code Playgroud)\n

  • 没有在“member.value”上使用“.lower()”,因为问题中发布的原始代码使用小写字母表示成员值,并且人们很容易找出并根据自己的情况进行调整。但是,无论如何,感谢您指出这一点 - 现在已经改变了。另外,我不建议使用`== value.lower()`,如上面的评论所示,而是在`for循环`之外将值转换为小写,否则,您将不必要地调用`.lower() ` 循环中每个枚举成员的函数。 (2认同)
  • 它已更改为“(str, Enum)”,但没有必要这样做,因为上面的示例使用每个枚举成员上的“value”属性来将值与字符串进行比较。但是,通过这样做,您也可以使用“if member.lower() == value”来代替。因此,在比较端点内的值时也可能很方便,因为您不必使用“value”属性。此外,如果“MyEnum”类的任何枚举成员包含非“str”类型的值(例如数字、日期),它们将自动转换为“str”。 (2认同)