I have the following Pydantic v1.10 custom datatype:
class SelectionList(ABC):
class_selection_list = []
datatype = None
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(selection_list=cls.class_selection_list, type=cls.datatype)
@classmethod
def validate(cls, v):
if v not in cls.class_selection_list:
raise Exception(f"Value must be on of this list: {str(cls.class_selection_list)}")
class StatusType(str, SelectionList):
class_selection_list = ["Active", "Inactive"]
datatype = "string"
And the following model:
class MyModel(BaseModel):
ID: int = Field(alias="Id", frozen=True)
STATUS: StatusType = Field(alias="Status")
The MyModel.schema()
works find but, when trying to validate a record using modeled_record = MyModel(**record)
, the field value, which is a valid value, is not taken and the field Status
of the model is None
.
What Am I missing?
What Am I missing?
Your validator is not returning a value, which is why the field value is None. You need:
class SelectionList(ABC):
class_selection_list = []
datatype = None
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def __modify_schema__(cls, field_schema):
breakpoint()
field_schema.update(selection_list=cls.class_selection_list, type=cls.datatype)
@classmethod
def validate(cls, v):
if v not in cls.class_selection_list:
raise Exception(
f"Value must be on of this list: {str(cls.class_selection_list)}"
)
return cls(v)
Note that we're returning cls(v)
instead of simply v
; the latter would make the value of the Status
field a str
, while returning cls(v)
makes the value of Status
a StatusType
.
For what you're doing in this example, it would be much simpler to use enum.StrEnum
:
from pydantic import BaseModel, Field
from enum import StrEnum
class StatusType(StrEnum):
ACTIVE = "Active"
INACTIVE = "Inactive"
class MyModel(BaseModel):
ID: int = Field(alias="Id", frozen=True)
STATUS: StatusType = Field(alias="Status")
That gets you the same behavior:
>>> MyModel(Id=1, Status="invalid")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for MyModel
Status
value is not a valid enumeration member; permitted: 'Active', 'Inactive' (type=type_error.enum; enum_values=[<StatusType.ACTIVE: 'Active'>, <StatusType.INACTIVE: 'Inactive'>])
>>> MyModel(Id=1, Status="Active")
MyModel(ID=1, STATUS=<StatusType.ACTIVE: 'Active'>)
>>> x=MyModel(Id=1, Status="Active")
>>> x.json()
'{"ID": 1, "STATUS": "Active"}'
And just because I mentioned it in a comment, here's a solution using Pydantic 2.x annotated validators (but note that it still makes more sense to use the StrEnum
solution instead):
from typing import Annotated
from pydantic import BaseModel, Field, AfterValidator
def stringInList(*valid_values):
def _validate(v):
if v not in valid_values:
raise ValueError(f"value must be one of {', '.join(valid_values)}")
return v
return _validate
StatusType = Annotated[str, AfterValidator(stringInList("Active", "Inactive"))]
class MyModel(BaseModel):
ID: int = Field(alias="Id", frozen=True)
STATUS: StatusType = Field(alias="Status")