diff --git a/changes/1933-PrettyWood.md b/changes/1933-PrettyWood.md new file mode 100644 index 0000000000..656d844f50 --- /dev/null +++ b/changes/1933-PrettyWood.md @@ -0,0 +1,2 @@ +**Breaking Change:** always validate only first sublevel items with `each_item`. +There were indeed some edge cases with some compound types where the validated items were the last sublevel ones. \ No newline at end of file diff --git a/pydantic/fields.py b/pydantic/fields.py index ebe2d56efa..84143a477d 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -518,10 +518,26 @@ def _type_analysis(self) -> None: # noqa: C901 (ignore complexity) self.sub_fields = [self._create_sub_type(self.type_, '_' + self.name)] def _create_sub_type(self, type_: Type[Any], name: str, *, for_keys: bool = False) -> 'ModelField': + if for_keys: + class_validators = None + else: + # validators for sub items should not have `each_item` as we want to check only the first sublevel + class_validators = { + k: Validator( + func=v.func, + pre=v.pre, + each_item=False, + always=v.always, + check_fields=v.check_fields, + skip_on_failure=v.skip_on_failure, + ) + for k, v in self.class_validators.items() + if v.each_item + } return self.__class__( type_=type_, name=name, - class_validators=None if for_keys else {k: v for k, v in self.class_validators.items() if v.each_item}, + class_validators=class_validators, model_config=self.model_config, ) diff --git a/tests/test_validators.py b/tests/test_validators.py index a77b8da024..dd76c322d3 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -575,6 +575,19 @@ def check_foobar(cls, v): assert Model(foobar={1: 1}).foobar == {1: 2} +def test_validation_each_item_one_sublevel(): + class Model(BaseModel): + foobar: List[Tuple[int, int]] + + @validator('foobar', each_item=True) + def check_foobar(cls, v: Tuple[int, int]) -> Tuple[int, int]: + v1, v2 = v + assert v1 == v2 + return v + + assert Model(foobar=[(1, 1), (2, 2)]).foobar == [(1, 1), (2, 2)] + + def test_key_validation(): class Model(BaseModel): foobar: Dict[int, int]