As pydantic got upgraded from v1 to v2, I need to migrate the following piece of code from @validator to @field_validator. I'm facing issues to understand how can I validate more than one field.
I understand that for values we can use info: ValidationInfo.
Does anyone know how could the piece of code be migrated to v2.
Thanks
class Task(BaseModel):
name: Optional[str]
pwd: Optional[str]
depends_on: Optional[Union[str, List[str]]] = []
features: Optional[Dict] = {}
@validator("name", "pwd", "depends_on", "features", always=True)
def validate_required_fields(cls, value, values, field):
task_type = values.get("task_type")
for task, fields in task_required.items():
if task_type == task and field.name in fields:
if not value:
raise ValueError(f"{field} is mandatory for task type '{task_type}'")
return value
The validate_default
is an option in Field
(docs).
As you correctly identified, ValidationInfo
(docs) now has all the information about the fields and the already validated data.
A v2 validator could look like the following. Although you do not specify where task_type
comes from (why is it not a permanent field?) and how task_required
is defined.
from typing import Any, Literal
from pydantic import (
BaseModel,
field_validator,
ValidationInfo,
Field,
ConfigDict,
)
task_required: dict[str, list[str]] = {
"basic": [],
"extra": ["depends_on"],
}
class Task(BaseModel):
task_type: Literal["basic", "extra"] = Field(
...,
description="Type of task",
)
name: str | None = Field(
...,
description="Name of the task",
)
pwd: str | None = Field(
...,
description="Working directory of the task",
)
depends_on: str | list[str] | None = Field(
default_factory=list,
description="Task dependencies",
validate_default=True,
)
features: dict | None = Field(
default_factory=dict,
description="Task features",
validate_default=True,
)
model_config = ConfigDict(extra="allow")
@field_validator("name", "pwd", "depends_on", "features")
@classmethod
def validate_required_fields(cls, value: Any, info: ValidationInfo):
task_type = info.data.get("task_type", "")
if task_type not in task_required:
raise ValueError(f'Invalid task type "{task_type}".')
required_fields = task_required[task_type]
if info.field_name in required_fields and not value:
raise ValueError(
f'Field "{info.field_name}" is required for task "{task_type}".'
)
return value
Task(task_type="extra", name="example", pwd=".")
#ValidationError: 1 validation error for Task
#depends_on
# Value error, Field "depends_on" is required for task "extra". [type=value_error, input_value=[], input_type=list]
# For further information visit https://errors.pydantic.dev/2.6/v/value_error
It seems that you actually have to define task_type
in the model. Otherwise, it was not available under info.data
.