I'm trying to update my code to pydantic v2 and having trouble finding a good way to replicate the custom types I had in version 1. I'll use my custom date type as an example. The original implementation and usage looked something like this:
from datetime import date
from pydantic import BaseModel
class CustomDate(date):
# Override POTENTIAL_FORMATS and fill it with date format strings to match your data
POTENTIAL_FORMATS = []
@classmethod
def __get_validators__(cls):
yield cls.validate_date
@classmethod
def validate_date(cls, field_value, values, field, config) -> date:
if type(field_value) is date:
return field_value
return to_date(field.name, field_value, cls.POTENTIAL_FORMATS, return_str=False)
class ExampleModel(BaseModel):
class MyDate(CustomDate):
POTENTIAL_FORMATS = ['%Y-%m-%d', '%Y/%m/%d']
dt: MyDate
I tried to follow the official docs and the examples laid out here below and it mostly worked, but the info
parameter does not have the fields I need (data
and field_name
). Attempting to access them gives me an AttributeError.
info.field_name
*** AttributeError: No attribute named 'field_name'
Both the Annotated
and __get_pydantic_core_schema__
approaches have this issue
from datetime import date
from typing import Annotated
from pydantic import BaseModel, BeforeValidator
from pydantic_core import core_schema
class CustomDate:
POTENTIAL_FORMATS = []
@classmethod
def validate(cls, field_value, info):
if type(field_value) is date:
return field_value
return to_date(info.field_name, field_value, potential_formats, return_str=False)
@classmethod
def __get_pydantic_core_schema__(cls, source, handler) -> core_schema.CoreSchema:
return core_schema.general_plain_validator_function(cls.validate)
def custom_date(potential_formats):
"""
:param potential_formats: A list of datetime format strings
"""
def validate_date(field_value, info) -> date:
if type(field_value) is date:
return field_value
return to_date(info.field_name, field_value, potential_formats, return_str=False)
CustomDate = Annotated[date, BeforeValidator(validate_date)]
return CustomDate
class ExampleModel(BaseModel):
class MyDate(CustomDate):
POTENTIAL_FORMATS = ['%Y-%m-%d', '%Y/%m/%d']
dt: MyDate
dt2: custom_date(['%Y-%m-%d', '%Y/%m/%d'])
If I just include the validate_date
function as a regular field_validator
I get info
with all the fields I need, it's only when using it with custom types that I see this issue. How do I write a custom type that has access to previously validated fields and the name of the field being validated?
As of version 2.4 you can get the field_name and data together. See the updated docs here.
Now the first version of my custom data type looks like:
class CustomDate:
POTENTIAL_FORMATS = []
@classmethod
def validate(cls, field_value, info):
if type(field_value) is date:
return field_value
return to_date(info.field_name, field_value, cls.POTENTIAL_FORMATS, return_str=False)
@classmethod
def __get_pydantic_core_schema__(cls, source, handler) -> core_schema.CoreSchema:
return core_schema.with_info_before_validator_function(
cls.validate, handler(date), field_name=handler.field_name
)
Where all I needed to change was which core_schema validator function I was using. The second version of my custom data type (the one using Annotated) now works as is with no changes.
It looks like accessing info.data
and info.field_name
inside a custom type validator is not currently possible in v2 according to this feature request.
If all you need is info.data
, then it looks like you can define your validator with core_schema.field_before_validator_function
(I'd guess all the field_*
validators work), although you will need to make up a field name:
from dataclasses import dataclass
from typing import Annotated, List, Any, Callable
from pydantic import ValidationError, BaseModel, Field, BeforeValidator, field_validator, GetCoreSchemaHandler
from pydantic_core import core_schema, CoreSchema
def fn(v: str, info: core_schema.ValidationInfo, *args, **kwargs) -> str:
try:
print(f'Validating {info.field_name}')
return info.data['use_this']
except AttributeError as err:
return 'No data'
class AsFieldB4Method(str):
@classmethod
def __get_pydantic_core_schema__(
cls, source_type: Any, handler: GetCoreSchemaHandler, *args, **kwargs
) -> CoreSchema:
return core_schema.field_before_validator_function(fn, 'not_the_real_field_name', core_schema.str_schema())
class MyModel(BaseModel):
use_this: str
core_schema_field_b4_method: AsFieldB4Method # Partially works
From the comments, it sounds like the pydantic team want to make it work with non-field validators and to make accessing info.field_name
possible, so hopefully that happens. I'll update this answer when the change happens, but check that link in case I missed it.