mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
5f634067da
* moving typing related objects into typing.py, fix #713 * cleanup
422 lines
10 KiB
Python
422 lines
10 KiB
Python
import sys
|
|
|
|
import pytest
|
|
|
|
from pydantic import ConfigError, ValidationError
|
|
|
|
skip_not_37 = pytest.mark.skipif(sys.version_info < (3, 7), reason='testing >= 3.7 behaviour only')
|
|
|
|
|
|
@skip_not_37
|
|
def test_postponed_annotations(create_module):
|
|
module = create_module(
|
|
"""
|
|
from __future__ import annotations
|
|
from pydantic import BaseModel
|
|
|
|
class Model(BaseModel):
|
|
a: int
|
|
"""
|
|
)
|
|
m = module.Model(a='123')
|
|
assert m.dict() == {'a': 123}
|
|
|
|
|
|
@skip_not_37
|
|
def test_postponed_annotations_optional(create_module):
|
|
module = create_module(
|
|
"""
|
|
from __future__ import annotations
|
|
from typing import Optional
|
|
from pydantic import BaseModel
|
|
|
|
class Model(BaseModel):
|
|
a: Optional[int]
|
|
"""
|
|
)
|
|
assert module.Model(a='123').dict() == {'a': 123}
|
|
assert module.Model().dict() == {'a': None}
|
|
|
|
|
|
def test_basic_forward_ref(create_module):
|
|
module = create_module(
|
|
"""
|
|
from typing import Optional
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
class Foo(BaseModel):
|
|
a: int
|
|
|
|
FooRef = ForwardRef('Foo')
|
|
|
|
class Bar(BaseModel):
|
|
b: Optional[FooRef]
|
|
"""
|
|
)
|
|
|
|
assert module.Bar().dict() == {'b': None}
|
|
assert module.Bar(b={'a': '123'}).dict() == {'b': {'a': 123}}
|
|
|
|
|
|
def test_self_forward_ref_module(create_module):
|
|
module = create_module(
|
|
"""
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
Foo = ForwardRef('Foo')
|
|
|
|
class Foo(BaseModel):
|
|
a: int = 123
|
|
b: 'Foo' = None
|
|
|
|
Foo.update_forward_refs()
|
|
"""
|
|
)
|
|
|
|
assert module.Foo().dict() == {'a': 123, 'b': None}
|
|
assert module.Foo(b={'a': '321'}).dict() == {'a': 123, 'b': {'a': 321, 'b': None}}
|
|
|
|
|
|
def test_self_forward_ref_collection(create_module):
|
|
module = create_module(
|
|
"""
|
|
from typing import List, Dict
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
Foo = ForwardRef('Foo')
|
|
|
|
class Foo(BaseModel):
|
|
a: int = 123
|
|
b: Foo = None
|
|
c: List[Foo] = []
|
|
d: Dict[str, Foo] = {}
|
|
|
|
Foo.update_forward_refs()
|
|
"""
|
|
)
|
|
|
|
assert module.Foo().dict() == {'a': 123, 'b': None, 'c': [], 'd': {}}
|
|
assert module.Foo(b={'a': '321'}, c=[{'a': 234}], d={'bar': {'a': 345}}).dict() == {
|
|
'a': 123,
|
|
'b': {'a': 321, 'b': None, 'c': [], 'd': {}},
|
|
'c': [{'a': 234, 'b': None, 'c': [], 'd': {}}],
|
|
'd': {'bar': {'a': 345, 'b': None, 'c': [], 'd': {}}},
|
|
}
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
module.Foo(b={'a': '321'}, c=[{'b': 234}], d={'bar': {'a': 345}})
|
|
assert exc_info.value.errors() == [
|
|
{'loc': ('c', 0, 'b'), 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}
|
|
]
|
|
|
|
|
|
def test_self_forward_ref_local(create_module):
|
|
module = create_module(
|
|
"""
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
def main():
|
|
Foo = ForwardRef('Foo')
|
|
|
|
class Foo(BaseModel):
|
|
a: int = 123
|
|
b: Foo = None
|
|
|
|
Foo.update_forward_refs()
|
|
return Foo
|
|
"""
|
|
)
|
|
Foo = module.main()
|
|
assert Foo().dict() == {'a': 123, 'b': None}
|
|
assert Foo(b={'a': '321'}).dict() == {'a': 123, 'b': {'a': 321, 'b': None}}
|
|
|
|
|
|
def test_missing_update_forward_refs(create_module):
|
|
module = create_module(
|
|
"""
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
Foo = ForwardRef('Foo')
|
|
|
|
class Foo(BaseModel):
|
|
a: int = 123
|
|
b: Foo = None
|
|
"""
|
|
)
|
|
with pytest.raises(ConfigError) as exc_info:
|
|
module.Foo(b=123)
|
|
assert str(exc_info.value).startswith('field "b" not yet prepared so type is still a ForwardRef')
|
|
|
|
|
|
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
|
|
|
|
@dataclass
|
|
class Dataclass:
|
|
url: UrlStr
|
|
"""
|
|
)
|
|
m = module.Dataclass('http://example.com ')
|
|
assert m.url == 'http://example.com'
|
|
|
|
|
|
def test_forward_ref_sub_types(create_module):
|
|
module = create_module(
|
|
"""
|
|
from typing import Union
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
class Leaf(BaseModel):
|
|
a: str
|
|
|
|
|
|
TreeType = Union[ForwardRef('Node'), Leaf]
|
|
|
|
|
|
class Node(BaseModel):
|
|
value: int
|
|
left: TreeType
|
|
right: TreeType
|
|
|
|
|
|
Node.update_forward_refs()
|
|
"""
|
|
)
|
|
Node = module.Node
|
|
Leaf = module.Leaf
|
|
data = {'value': 3, 'left': {'a': 'foo'}, 'right': {'value': 5, 'left': {'a': 'bar'}, 'right': {'a': 'buzz'}}}
|
|
|
|
node = Node(**data)
|
|
assert isinstance(node.left, Leaf)
|
|
assert isinstance(node.right, Node)
|
|
|
|
|
|
def test_forward_ref_nested_sub_types(create_module):
|
|
module = create_module(
|
|
"""
|
|
from typing import Tuple, Union
|
|
from pydantic import BaseModel
|
|
from pydantic.typing import ForwardRef
|
|
|
|
class Leaf(BaseModel):
|
|
a: str
|
|
|
|
|
|
TreeType = Union[Union[Tuple[ForwardRef('Node'), str], int], Leaf]
|
|
|
|
|
|
class Node(BaseModel):
|
|
value: int
|
|
left: TreeType
|
|
right: TreeType
|
|
|
|
|
|
Node.update_forward_refs()
|
|
"""
|
|
)
|
|
Node = module.Node
|
|
Leaf = module.Leaf
|
|
data = {
|
|
'value': 3,
|
|
'left': {'a': 'foo'},
|
|
'right': [{'value': 5, 'left': {'a': 'bar'}, 'right': {'a': 'buzz'}}, 'test'],
|
|
}
|
|
|
|
node = Node(**data)
|
|
assert isinstance(node.left, Leaf)
|
|
assert isinstance(node.right[0], Node)
|
|
|
|
|
|
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
|
|
|
|
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'],
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
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
|
|
|
|
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'],
|
|
},
|
|
},
|
|
}
|