I'm migrating from v1 to v2 of Pydantic and I'm attempting to replace all uses of the deprecated @validator with @field_validator.
Previously, I was using the values
argument to my validator function to reference the values of other previously validated fields. As the v1 docs say:
You can also add any subset of the following arguments to the signature (the names must match):
- values: a dict containing the name-to-value mapping of any previously-validated fields
It seems this values
argument is no longer passed as the @field_validator
signature has changed. However, the migration docs don't mention a values
equivalent in v2 and the v2 validator documentation page has not yet been updated for v2.0.
Does anyone know the preferred approach for v2?
V1 validator:
@validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
The current version of the Pydantic v2 documentation is actually up to date for the field validators section in terms of what the signature of your validation method must/can look like.
- the second argument is the field value to validate; it can be named as you please
- the third argument is an instance of
pydantic.ValidationInfo
[...]
If you want to access values from another field inside a
@field_validator
, this may be possible usingValidationInfo.data
, which is a dict of field name to field value. Validation is done in the order fields are defined, so you have to be careful when usingValidationInfo.data
to not access a field that has not yet been validated/populated [...]
(Side note: You can look up a bit more information about the ValidationData
protocol in the Pydantic Core API reference, although it is a bit terse and could do with some cross-references.)
Say you had the following code in Pydantic v1:
from typing import Any
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
...
password1: str
password2: str
@validator("password2")
def passwords_match(cls, v: str, values: dict[str, Any]) -> str:
if "password1" in values and v != values["password1"]:
raise ValueError("passwords do not match")
return v
try:
UserModel(password1="abc", password2="xyz")
except ValidationError as err:
print(err.json(indent=4))
Output:
[
{
"loc": [
"password2"
],
"msg": "passwords do not match",
"type": "value_error"
}
]
@field_validator
You would have to rewrite the v1 code above like this in v2:
from pydantic import BaseModel, ValidationError, ValidationInfo, field_validator
class UserModel(BaseModel):
...
password1: str
password2: str
@field_validator("password2")
def passwords_match(cls, v: str, info: ValidationInfo) -> str:
if "password1" in info.data and v != info.data["password1"]:
raise ValueError("passwords do not match")
return v
try:
UserModel(password1="abc", password2="xyz")
except ValidationError as err:
print(err.json(indent=4))
v2 output:
[
{
"type": "value_error",
"loc": [
"password2"
],
"msg": "Value error, passwords do not match",
"input": "xyz",
"ctx": {
"error": "passwords do not match"
},
"url": "https://errors.pydantic.dev/2.6/v/value_error"
}
]
For the sake of completeness, Pydantic v2 offers a new way of validating fields, which is annotated validators. The code above could just as easily be written with an AfterValidator
(for example) like this:
from typing import Annotated
from pydantic import AfterValidator, BaseModel, ValidationError, ValidationInfo
def ensure_passwords_match(v: str, info: ValidationInfo) -> str:
if "password1" in info.data and v != info.data["password1"]:
raise ValueError("passwords do not match")
return v
class UserModel(BaseModel):
...
password1: str
password2: Annotated[str, AfterValidator(ensure_passwords_match)]
try:
UserModel(password1="abc", password2="xyz")
except ValidationError as err:
print(err.json(indent=4))
The output is exactly the same as in the @field_validator
example.
As you can see from the Pydantic core API docs linked above, annotated validator constructors take the same type of argument as the decorator returned by @field_validator
, namely either a NoInfoValidatorFunction
or a WithInfoValidatorFunction
, so either a Callable[[Any], Any]
or a Callable[[Any, ValidationInfo], Any]
.
(Strictly speaking the signature of the field_validator
inner decorator is different because it technically can deal with more nuances like implicit classmethods etc., but it is designed to essentially deal with the same type of functions.)
Field-specific validator functions should therefore always have either
ValidationInfo
object.