mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Merge pull request #706 from koxudaxi/support_forwardred_in_python36
support ForwardRef in Python 3.6
This commit is contained in:
+1
-1
@@ -2,12 +2,12 @@
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
v0.32 (unreleased)
|
||||
..................
|
||||
* add model name to ``ValidationError`` error message, #676 by @dmontagu
|
||||
* **breaking change**: remove ``__getattr__`` and rename ``__values__`` to ``__dict__`` on ``BaseModel``,
|
||||
deprecation warning on use ``__values__`` attr, attributes access speed increased up to 14 times, #712 by @MrMrRobat
|
||||
* support ``ForwardRef`` (without self-referencing annotations) in Python3.6, #706 by @koxudaxi
|
||||
|
||||
v0.31.1 (2019-07-31)
|
||||
....................
|
||||
|
||||
+2
-2
@@ -277,7 +277,7 @@ The ellipsis ``...`` just means "Required" same as annotation only declarations
|
||||
Self-referencing Models
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since ``python 3.7``, data structures with self-referencing models are also supported, provided the function
|
||||
Data structures with self-referencing models are also supported, provided the function
|
||||
``update_forward_refs()`` is called once the model is created (you will be reminded
|
||||
with a friendly error message if you don't).
|
||||
|
||||
@@ -287,7 +287,7 @@ Within the model, you can refer to the not-yet-constructed model by a string :
|
||||
|
||||
(This script is complete, it should run "as is")
|
||||
|
||||
You can also refer it by its type, provided you import ``annotations`` (see
|
||||
Since ``python 3.7``, You can also refer it by its type, provided you import ``annotations`` (see
|
||||
:ref:`the relevant paragraph <postponed_annotations>` for support depending on Python
|
||||
and pydantic versions).
|
||||
|
||||
|
||||
+1
-3
@@ -198,9 +198,7 @@ class MetaModel(ABCMeta):
|
||||
f.prepare()
|
||||
|
||||
set_extra(config, name)
|
||||
annotations = namespace.get('__annotations__', {})
|
||||
if sys.version_info >= (3, 7):
|
||||
annotations = resolve_annotations(annotations, namespace.get('__module__', None))
|
||||
annotations = resolve_annotations(namespace.get('__annotations__', {}), namespace.get('__module__', None))
|
||||
|
||||
class_vars = set()
|
||||
if (namespace.get('__module__'), namespace.get('__qualname__')) != ('pydantic.main', 'BaseModel'):
|
||||
|
||||
+15
-3
@@ -42,9 +42,18 @@ except ImportError:
|
||||
|
||||
try:
|
||||
from typing import ForwardRef # type: ignore
|
||||
|
||||
def evaluate_forwardref(type_, globalns, localns): # type: ignore
|
||||
return type_._evaluate(globalns, localns)
|
||||
|
||||
|
||||
except ImportError:
|
||||
# python 3.6
|
||||
ForwardRef = None
|
||||
from typing import _ForwardRef as ForwardRef # type: ignore
|
||||
|
||||
def evaluate_forwardref(type_, globalns, localns): # type: ignore
|
||||
return type_._eval_type(globalns, localns)
|
||||
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .main import BaseModel # noqa: F401
|
||||
@@ -277,7 +286,10 @@ def resolve_annotations(raw_annotations: Dict[str, AnyType], module_name: Option
|
||||
annotations = {}
|
||||
for name, value in raw_annotations.items():
|
||||
if isinstance(value, str):
|
||||
value = ForwardRef(value, is_argument=False)
|
||||
if sys.version_info >= (3, 7):
|
||||
value = ForwardRef(value, is_argument=False)
|
||||
else:
|
||||
value = ForwardRef(value)
|
||||
try:
|
||||
value = _eval_type(value, base_globals, None)
|
||||
except NameError:
|
||||
@@ -335,7 +347,7 @@ def update_field_forward_refs(field: 'Field', globalns: Any, localns: Any) -> No
|
||||
Try to update ForwardRefs on fields based on this Field, globalns and localns.
|
||||
"""
|
||||
if type(field.type_) == ForwardRef:
|
||||
field.type_ = field.type_._evaluate(globalns, localns or None) # type: ignore
|
||||
field.type_ = evaluate_forwardref(field.type_, globalns, localns or None)
|
||||
field.prepare()
|
||||
if field.sub_fields:
|
||||
for sub_f in field.sub_fields:
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
"""
|
||||
Tests for python 3.7 behaviour, eg postponed annotations and ForwardRef.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
@@ -41,12 +38,12 @@ class Model(BaseModel):
|
||||
assert module.Model().dict() == {'a': None}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_basic_forward_ref(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef, Optional
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
class Foo(BaseModel):
|
||||
a: int
|
||||
@@ -62,18 +59,17 @@ class Bar(BaseModel):
|
||||
assert module.Bar(b={'a': '123'}).dict() == {'b': {'a': 123}}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_self_forward_ref_module(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef
|
||||
from pydantic import BaseModel
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
Foo = ForwardRef('Foo')
|
||||
|
||||
class Foo(BaseModel):
|
||||
a: int = 123
|
||||
b: Foo = None
|
||||
b: 'Foo' = None
|
||||
|
||||
Foo.update_forward_refs()
|
||||
"""
|
||||
@@ -83,12 +79,12 @@ Foo.update_forward_refs()
|
||||
assert module.Foo(b={'a': '321'}).dict() == {'a': 123, 'b': {'a': 321, 'b': None}}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_self_forward_ref_collection(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef, List, Dict
|
||||
from typing import List, Dict
|
||||
from pydantic import BaseModel
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
Foo = ForwardRef('Foo')
|
||||
|
||||
@@ -117,12 +113,11 @@ Foo.update_forward_refs()
|
||||
]
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_self_forward_ref_local(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef
|
||||
from pydantic import BaseModel
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
def main():
|
||||
Foo = ForwardRef('Foo')
|
||||
@@ -140,12 +135,11 @@ def main():
|
||||
assert Foo(b={'a': '321'}).dict() == {'a': 123, 'b': {'a': 321, 'b': None}}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_missing_update_forward_refs(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef
|
||||
from pydantic import BaseModel
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
Foo = ForwardRef('Foo')
|
||||
|
||||
@@ -159,10 +153,25 @@ class Foo(BaseModel):
|
||||
assert str(exc_info.value).startswith('field "b" not yet prepared so type is still a ForwardRef')
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_forward_ref_dataclass(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from pydantic import UrlStr
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Dataclass:
|
||||
url: UrlStr
|
||||
"""
|
||||
)
|
||||
m = module.Dataclass('http://example.com ')
|
||||
assert m.url == 'http://example.com'
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_forward_ref_dataclass_with_future_annotations(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pydantic import UrlStr
|
||||
from pydantic.dataclasses import dataclass
|
||||
@@ -176,14 +185,12 @@ class Dataclass:
|
||||
assert m.url == 'http://example.com'
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_forward_ref_sub_types(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef, Union
|
||||
|
||||
from typing import Union
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
class Leaf(BaseModel):
|
||||
a: str
|
||||
@@ -210,14 +217,12 @@ Node.update_forward_refs()
|
||||
assert isinstance(node.right, Node)
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_forward_ref_nested_sub_types(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import ForwardRef, Tuple, Union
|
||||
|
||||
from typing import Tuple, Union
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pydantic.utils import ForwardRef
|
||||
|
||||
class Leaf(BaseModel):
|
||||
a: str
|
||||
@@ -248,10 +253,45 @@ Node.update_forward_refs()
|
||||
assert isinstance(node.right[0], Node)
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_self_reference_json_schema(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import List
|
||||
from pydantic import BaseModel, Schema
|
||||
|
||||
class Account(BaseModel):
|
||||
name: str
|
||||
subaccounts: List['Account'] = []
|
||||
|
||||
Account.update_forward_refs()
|
||||
"""
|
||||
)
|
||||
Account = module.Account
|
||||
assert Account.schema() == {
|
||||
'$ref': '#/definitions/Account',
|
||||
'definitions': {
|
||||
'Account': {
|
||||
'title': 'Account',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'title': 'Name', 'type': 'string'},
|
||||
'subaccounts': {
|
||||
'title': 'Subaccounts',
|
||||
'default': [],
|
||||
'type': 'array',
|
||||
'items': {'$ref': '#/definitions/Account'},
|
||||
},
|
||||
},
|
||||
'required': ['name'],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_self_reference_json_schema_with_future_annotations(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import List
|
||||
from pydantic import BaseModel, Schema
|
||||
@@ -285,10 +325,57 @@ Account.update_forward_refs()
|
||||
}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_circular_reference_json_schema(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from typing import List
|
||||
from pydantic import BaseModel, Schema
|
||||
|
||||
class Owner(BaseModel):
|
||||
account: 'Account'
|
||||
|
||||
class Account(BaseModel):
|
||||
name: str
|
||||
owner: 'Owner'
|
||||
subaccounts: List['Account'] = []
|
||||
|
||||
Account.update_forward_refs()
|
||||
Owner.update_forward_refs()
|
||||
"""
|
||||
)
|
||||
Account = module.Account
|
||||
assert Account.schema() == {
|
||||
'$ref': '#/definitions/Account',
|
||||
'definitions': {
|
||||
'Account': {
|
||||
'title': 'Account',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'title': 'Name', 'type': 'string'},
|
||||
'owner': {'$ref': '#/definitions/Owner'},
|
||||
'subaccounts': {
|
||||
'title': 'Subaccounts',
|
||||
'default': [],
|
||||
'type': 'array',
|
||||
'items': {'$ref': '#/definitions/Account'},
|
||||
},
|
||||
},
|
||||
'required': ['name', 'owner'],
|
||||
},
|
||||
'Owner': {
|
||||
'title': 'Owner',
|
||||
'type': 'object',
|
||||
'properties': {'account': {'$ref': '#/definitions/Account'}},
|
||||
'required': ['account'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@skip_not_37
|
||||
def test_circular_reference_json_schema_with_future_annotations(create_module):
|
||||
module = create_module(
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import List
|
||||
from pydantic import BaseModel, Schema
|
||||
Reference in New Issue
Block a user