Python Enum 和 Pydantic : 接受 enum 成员的组合

Python Enum and Pydantic : accept enum member's composition

我有一个枚举:

from enum import Enum

class MyEnum(Enum):
    val1 = "val1"
    val2 = "val2"
    val3 = "val3"

我想根据该枚举验证一个 pydantic 字段。

from pydantic import BaseModel

class MyModel(BaseModel):
    my_enum_field: MyEnum

但我希望此验证也接受由 Enum 成员组成的字符串。

例如:“val1_val2_val3”或“val1_val3”是有效输入。

我无法将此字段作为带有验证器的字符串字段,因为我使用需要此类型的测试库 (hypothesis and pydantic-factories) 以呈现枚举中的值之一(用于模拟随机输入)

所以这个 :

from pydantic import BaseModel, validator

class MyModel(BaseModel):
    my_enum_field: str

    @validator('my_enum_field', pre=True)
    def validate_my_enum_field(cls, value):
        split_val = str(value).split('_')
        if not all(v in MyEnum._value2member_map_ for v in split_val):
            raise ValueError()
        return value

可以工作,但破坏了我的测试套件,因为该字段不再是枚举类型。

如何将此字段保留为枚举类型(以使我的模拟结构仍然有效)并使 pydantic 同时接受复合值?

到目前为止,我尝试动态扩展枚举,但没有成功。

我对此进行了更深入的研究,我相信类似的内容可能会有所帮助。您可以创建一个新的 class 来定义作为枚举值列表的 属性。

此 class 可以提供自定义的 validate 方法并提供 __modify_schema__ 以保持有关在 json 架构中作为字符串的信息。

我们可以为这样的串联枚举的通用列表定义一个基础 class:

from typing import Generic, TypeVar, Type
from enum import Enum

T = TypeVar("T", bound=Enum)


class ConcatenatedEnum(Generic[T], list[T]):
    enum_type: Type[T]

    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, value: str):
        return list(map(cls.enum_type, value.split("_")))

    @classmethod
    def __modify_schema__(cls, field_schema: dict):
        all_values = ', '.join(f"'{ex.value}'" for ex in cls.enum_type)
        field_schema.update(
            title=f"Concatenation of {cls.enum_type.__name__} values",
            description=f"Underscore delimited list of values {all_values}",
            type="string",
        )
        if "items" in field_schema:
            del field_schema["items"]

__modify_schema__ 方法中,我还提供了一种方法来生成对哪些值有效的描述。

要在您的应用程序中使用它:

class MyEnum(Enum):
    val1 = "val1"
    val2 = "val2"
    val3 = "val3"


class MyEnumList(ConcatenatedEnum[MyEnum]):
    enum_type = MyEnum


class MyModel(BaseModel):
    my_enum_field: MyEnumList

示例模型:

print(MyModel.parse_obj({"my_enum_field": "val1"}))
print(MyModel.parse_obj({"my_enum_field": "val1_val2"}))
my_enum_field=[<MyEnum.val1: 'val1'>]
my_enum_field=[<MyEnum.val1: 'val1'>, <MyEnum.val2: 'val2'>]

示例架构:

print(json.dumps(MyModel.schema(), indent=2))
{
  "title": "MyModel",
  "type": "object",
  "properties": {
    "my_enum_field": {
      "title": "Concatenation of MyEnum values",
      "description": "Underscore delimited list of values 'val1', 'val2', 'val3'",
      "type": "string"
    }
  },
  "required": [
    "my_enum_field"
  ]
}