Skip to content

Commit

Permalink
fix: check only first sublevel for validators with each_item (#1991)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
PrettyWood committed Feb 11, 2021
1 parent 60ef8cc commit f05bdb7
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 1 deletion.
2 changes: 2 additions & 0 deletions 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.
18 changes: 17 additions & 1 deletion pydantic/fields.py
Expand Up @@ -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,
)

Expand Down
13 changes: 13 additions & 0 deletions tests/test_validators.py
Expand Up @@ -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]
Expand Down

0 comments on commit f05bdb7

Please sign in to comment.