Skip to content

Commit

Permalink
fix: validate nested models with default_factory
Browse files Browse the repository at this point in the history
PR pydantic#1504 introduced a regression by bypassing `populate_validators()`,
which would skip the validation of children in nested models
with `default_factory`

closes pydantic#1710
  • Loading branch information
PrettyWood committed Jul 12, 2020
1 parent 5dbb127 commit 458dc13
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 10 deletions.
1 change: 1 addition & 0 deletions changes/1710-PrettyWood.md
@@ -0,0 +1 @@
fix validation of nested models with `default_factory`
26 changes: 16 additions & 10 deletions pydantic/fields.py
Expand Up @@ -341,8 +341,22 @@ def prepare(self) -> None:
e.g. calling it it multiple times may modify the field and configure it incorrectly.
"""

# To prevent side effects by calling the `default_factory` for nothing, we only call it
# when we want to validate the default value i.e. when `validate_all` is set to True.
self._set_default_and_type()
self._type_analysis()
if self.required is Undefined:
self.required = True
self.field_info.default = Required
if self.default is Undefined and self.default_factory is None:
self.default = None
self.populate_validators()

def _set_default_and_type(self) -> None:
"""
Set the default value, infer the type if needed and check if `None` value is valid.
Note: to prevent side effects by calling the `default_factory` for nothing, we only call it
when we want to validate the default value i.e. when `validate_all` is set to True.
"""
if self.default_factory is not None:
if self.type_ is None:
raise errors_.ConfigError(
Expand All @@ -368,14 +382,6 @@ def prepare(self) -> None:
if self.required is False and default_value is None:
self.allow_none = True

self._type_analysis()
if self.required is Undefined:
self.required = True
self.field_info.default = Required
if self.default is Undefined and self.default_factory is None:
self.default = None
self.populate_validators()

def _type_analysis(self) -> None: # noqa: C901 (ignore complexity)
# typing interface is horrible, we have to do some ugly checks
if lenient_issubclass(self.type_, JsonWrapper):
Expand Down
12 changes: 12 additions & 0 deletions tests/test_main.py
Expand Up @@ -1174,6 +1174,18 @@ class MyModel(BaseModel):
assert m2.id == 2


def test_default_factory_validate_children():
class Child(BaseModel):
x: int

class Parent(BaseModel):
children: List[Child] = Field(default_factory=list)

Parent(children=[{'x': 1}, {'x': 2}])
with pytest.raises(ValidationError):
Parent(children=[{'x': 1}, {'y': 2}])


@pytest.mark.skipif(sys.version_info < (3, 7), reason='field constraints are set but not enforced with python 3.6')
def test_none_min_max_items():
# None default
Expand Down

0 comments on commit 458dc13

Please sign in to comment.