From a2fc01a59bf1d3500f651ba28ef809671f323aab Mon Sep 17 00:00:00 2001 From: PrettyWood Date: Thu, 8 Oct 2020 21:26:30 +0200 Subject: [PATCH] fix: forward ref with nested models and optional fields (#1752) * fix: forward ref with nested models and optional fields PR #1712 introduced a regression for forward refs in `ModelField.prepare` as it would not return early for forward refs anymore. Optional fields would hence have `required` set to `True`. closes #1736 * test: skip python 3.6 as __future__.annotations is not defined --- changes/1736-PrettyWood.md | 1 + pydantic/fields.py | 10 +++++----- tests/test_forward_ref.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 changes/1736-PrettyWood.md diff --git a/changes/1736-PrettyWood.md b/changes/1736-PrettyWood.md new file mode 100644 index 0000000..64a22ab --- /dev/null +++ b/changes/1736-PrettyWood.md @@ -0,0 +1 @@ +Fix behaviour with forward refs and optional fields in nested models \ No newline at end of file diff --git a/pydantic/fields.py b/pydantic/fields.py index 6ab039c..c162616 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -334,6 +334,11 @@ class ModelField(Representation): """ self._set_default_and_type() + if self.type_.__class__ == ForwardRef: + # self.type_ is currently a ForwardRef and there's nothing we can do now, + # user will need to call model.update_forward_refs() + return + self._type_analysis() if self.required is Undefined: self.required = True @@ -366,11 +371,6 @@ class ModelField(Representation): if self.type_ is None: raise errors_.ConfigError(f'unable to infer type for attribute "{self.name}"') - if self.type_.__class__ == ForwardRef: - # self.type_ is currently a ForwardRef and there's nothing we can do now, - # user will need to call model.update_forward_refs() - return - if self.required is False and default_value is None: self.allow_none = True diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py index d640dd7..420079d 100644 --- a/tests/test_forward_ref.py +++ b/tests/test_forward_ref.py @@ -439,3 +439,36 @@ else: raise AssertionError('error not raised') """ ) + + +@skip_pre_37 +def test_forward_ref_optional(create_module): + module = create_module( + """ +from __future__ import annotations +from pydantic import BaseModel, Field +from typing import List, Optional + + +class Spec(BaseModel): + spec_fields: List[str] = Field(..., alias="fields") + filter: Optional[str] + sort: Optional[str] + + +class PSpec(Spec): + g: Optional[GSpec] + + +class GSpec(Spec): + p: Optional[PSpec] + +PSpec.update_forward_refs() + +class Filter(BaseModel): + g: Optional[GSpec] + p: Optional[PSpec] + """ + ) + Filter = module.Filter + assert isinstance(Filter(p={'sort': 'some_field:asc', 'fields': []}), Filter)