From f05bdb732f615ab7c8274c143d442f0a0db10135 Mon Sep 17 00:00:00 2001 From: Eric Jolibois Date: Thu, 11 Feb 2021 12:23:31 +0100 Subject: [PATCH] fix: check only first sublevel for validators with `each_item` (#1991) * fix: check only first sublevel for validators with `each_item` When using a validator with `each_item`, the items are all validated one by one. But if the items are also iterable the subitems would then be validated because the validator would be kept as it is. Now the validator passed to the items is changed and won't be propagated closes #1933 * chore: add breaking change --- changes/1933-PrettyWood.md | 2 ++ pydantic/fields.py | 18 +++++++++++++++++- tests/test_validators.py | 13 +++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 changes/1933-PrettyWood.md 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]