Reputation: 700
I have multiple pydantic 2.x models and instead of applying validation per each literal field on each model
class MyModel(BaseModel):
name: str = ""
description: Optional[str] = None
sex: Literal["male", "female"]
@field_validator("sex", mode="before")
@classmethod
def strip_sex(cls, v: Any, info: ValidationInfo):
if isinstance(v, str):
return v.strip()
return v
I want to use approach similar to this Annotated Validators
How can I achieve automatic validation on all Literal
fields?
def strip_literals(v: Any) -> Any:
if isinstance(v, str):
return v.strip()
return v
# doesn't work
# LiteralType = TypeVar("LiteralType", bound=Literal)
# LiteralStripped = Annotated[Literal, BeforeValidator(strip_literals)]
class MyModel(BaseModel):
name: str = ""
description: Optional[str] = None
sex: LiteralStripped["male", "female"]
I want something like above, but cannot actually define proper validation handlers on literals.
Upvotes: 2
Views: 757
Reputation: 1638
You have to move the declaration of the Literal values into the annotation, like so:
from typing import Any, Optional, Annotated, TypeVar, Literal
from pydantic import BaseModel, BeforeValidator
def strip_literals(v: Any) -> Any:
if isinstance(v, str):
return v.strip()
return v
LiteralStripped = Annotated[Literal["male", "female"], BeforeValidator(strip_literals)]
class MyModel(BaseModel):
name: str = ""
description: Optional[str] = None
sex: LiteralStripped
m = MyModel(sex="foo")
Which raises:
ValidationError: 1 validation error for MyModel
sex
Input should be 'male' or 'female' [type=literal_error, input_value='foo', input_type=str]
For further information visit https://errors.pydantic.dev/2.5/v/literal_error
In case you would like to avoid the repetition of the annotation, for different Literal values you can define an alias and rely on __class_getitem__
:
from typing import Any, Optional, Annotated, TypeVar, Literal
from pydantic import BaseModel, BeforeValidator
class LiteralStripped:
@staticmethod
def strip_literals(v: Any) -> Any:
if isinstance(v, str):
return v.strip()
return v
def __class_getitem__(cls, values):
return Annotated[Literal[values], BeforeValidator(cls.strip_literals)]
class MyModel(BaseModel):
name: str = ""
description: Optional[str] = None
sex: LiteralStripped["male", "female"]
m = MyModel(sex="foo")
However this is already an advanced pattern, but it works exactly as you suggested.
I hope this helps.
Upvotes: 3