I'd like to define once for all fields_validators function in a BaseModel class, and inherit this class in my model, and the validators should apply to the updated class attributes.
MWE
def to_int(v: Union[str, int]) -> int:
if isinstance(v, str):
if v.startswith("0x"):
return int(v, 16)
return int(v)
return v
def to_bytes(v: Union[str, bytes, list[int]]) -> bytes:
if isinstance(v, bytes):
return v
elif isinstance(v, str):
if v.startswith("0x"):
return bytes.fromhex(v[2:])
return v.encode()
else:
return bytes(v)
class BaseModelCamelCase(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
alias_generator=AliasGenerator(
validation_alias=lambda name: AliasChoices(to_camel(name), name)
),
)
# FIXME: should apply to int type from get_type_hints only
@field_validator("*", mode="before")
def to_int(cls, v: Union[str, int]) -> int:
return to_int(v)
# FIXME: should apply to bytes type from get_type_hints only
@field_validator("*", mode="before")
def to_bytes(cls, v: Union[str, bytes, list[int]]) -> bytes:
return to_bytes(v)
class BaseTransactionModel(BaseModelCamelCase):
nonce: int
gas: int = Field(validation_alias=AliasChoices("gasLimit", "gas_limit", "gas"))
to: Optional[bytes]
value: int
data: bytes
r: int = 0
s: int = 0
I tried to use model_validate
but then I lost the alias parsing
To automatically add field validators based on type hints in Pydantic, you can use the Annotated type from typing_extensions along with Pydantic’s validator functions. This allows you to bind validation logic directly to a type, making your code more modular and reusable.
Here’s an example of how you can achieve this:
from typing import Any, List
from typing_extensions import Annotated
from pydantic import BaseModel, ValidationError
from pydantic.functional_validators import AfterValidator
# Define a validator function
def check_positive(value: int) -> int:
if value <= 0:
raise ValueError(f'{value} is not a positive number')
return value
# Create an annotated type with the validator
PositiveInt = Annotated[int, AfterValidator(check_positive)]
# Use the annotated type in a Pydantic model
class MyModel(BaseModel):
positive_number: PositiveInt
# Example usage
try:
model = MyModel(positive_number=10)
print(model)
except ValidationError as e:
print(e)
try:
model = MyModel(positive_number=-5)
except ValidationError as e:
print(e)
Explanation: Validator Function: check_positive ensures the value is a positive integer. Annotated Type: PositiveInt combines the int type with the check_positive validator using Annotated. Pydantic Model: MyModel uses PositiveInt for the positive_number field, automatically applying the validator. Benefits: Modularity: Validators are tied to types, making them reusable across different models. Clarity: The validation logic is separated from the model definition, improving readability.