mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
8301f9e4b2
* dataclass validation, fix #273 * support for tuple or dict in dataclasses * tweaks * adding example in docs
263 lines
5.7 KiB
Python
263 lines
5.7 KiB
Python
import dataclasses
|
|
|
|
import pytest
|
|
|
|
import pydantic
|
|
from pydantic import BaseModel, ValidationError
|
|
|
|
|
|
def test_simple():
|
|
@pydantic.dataclasses.dataclass
|
|
class MyDataclass:
|
|
a: int
|
|
b: float
|
|
|
|
d = MyDataclass('1', '2.5')
|
|
assert d.a == 1
|
|
assert d.b == 2.5
|
|
d = MyDataclass(b=10, a=20)
|
|
assert d.a == 20
|
|
assert d.b == 10
|
|
|
|
|
|
def test_value_error():
|
|
@pydantic.dataclasses.dataclass
|
|
class MyDataclass:
|
|
a: int
|
|
b: int
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
MyDataclass(1, 'wrong')
|
|
|
|
assert exc_info.value.errors() == [
|
|
{'loc': ('b',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
|
|
]
|
|
|
|
|
|
def test_frozen():
|
|
@pydantic.dataclasses.dataclass(frozen=True)
|
|
class MyDataclass:
|
|
a: int
|
|
|
|
d = MyDataclass(1)
|
|
assert d.a == 1
|
|
|
|
with pytest.raises(AttributeError):
|
|
d.a = 7
|
|
|
|
|
|
def test_validate_assignment():
|
|
class Config:
|
|
validate_assignment = True
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class MyDataclass:
|
|
a: int
|
|
|
|
d = MyDataclass(1)
|
|
assert d.a == 1
|
|
|
|
d.a = '7'
|
|
assert d.a == 7
|
|
|
|
|
|
def test_validate_assignment_error():
|
|
class Config:
|
|
validate_assignment = True
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class MyDataclass:
|
|
a: int
|
|
|
|
d = MyDataclass(1)
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
d.a = 'xxx'
|
|
assert exc_info.value.errors() == [
|
|
{'loc': ('a',), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
|
|
]
|
|
|
|
|
|
def test_not_validate_assignment():
|
|
@pydantic.dataclasses.dataclass
|
|
class MyDataclass:
|
|
a: int
|
|
|
|
d = MyDataclass(1)
|
|
assert d.a == 1
|
|
|
|
d.a = '7'
|
|
assert d.a == '7'
|
|
|
|
|
|
def test_post_init():
|
|
post_init_called = False
|
|
|
|
@pydantic.dataclasses.dataclass
|
|
class MyDataclass:
|
|
a: int
|
|
|
|
def __post_init__(self):
|
|
nonlocal post_init_called
|
|
post_init_called = True
|
|
|
|
d = MyDataclass('1')
|
|
assert d.a == 1
|
|
assert post_init_called
|
|
|
|
|
|
def test_inheritance():
|
|
@pydantic.dataclasses.dataclass
|
|
class A:
|
|
a: str = None
|
|
|
|
@pydantic.dataclasses.dataclass
|
|
class B(A):
|
|
b: int = None
|
|
|
|
b = B(a='a', b=12)
|
|
assert b.a == 'a'
|
|
assert b.b == 12
|
|
|
|
with pytest.raises(ValidationError):
|
|
B(a='a', b='b')
|
|
|
|
|
|
def test_validate_long_string_error():
|
|
class Config:
|
|
max_anystr_length = 3
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class MyDataclass:
|
|
a: str
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
MyDataclass('xxxx')
|
|
|
|
assert exc_info.value.errors() == [
|
|
{
|
|
'loc': ('a',),
|
|
'msg': 'ensure this value has at most 3 characters',
|
|
'type': 'value_error.any_str.max_length',
|
|
'ctx': {'limit_value': 3},
|
|
}
|
|
]
|
|
|
|
|
|
def test_validate_assigment_long_string_error():
|
|
class Config:
|
|
max_anystr_length = 3
|
|
validate_assignment = True
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class MyDataclass:
|
|
a: str
|
|
|
|
d = MyDataclass('xxx')
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
d.a = 'xxxx'
|
|
|
|
assert issubclass(MyDataclass.__pydantic_model__.__config__, BaseModel.Config)
|
|
assert exc_info.value.errors() == [
|
|
{
|
|
'loc': ('a',),
|
|
'msg': 'ensure this value has at most 3 characters',
|
|
'type': 'value_error.any_str.max_length',
|
|
'ctx': {'limit_value': 3},
|
|
}
|
|
]
|
|
|
|
|
|
def test_no_validate_assigment_long_string_error():
|
|
class Config:
|
|
max_anystr_length = 3
|
|
validate_assignment = False
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class MyDataclass:
|
|
a: str
|
|
|
|
d = MyDataclass('xxx')
|
|
d.a = 'xxxx'
|
|
|
|
assert d.a == 'xxxx'
|
|
|
|
|
|
def test_nested_dataclass():
|
|
@pydantic.dataclasses.dataclass
|
|
class Nested:
|
|
number: int
|
|
|
|
@pydantic.dataclasses.dataclass
|
|
class Outer:
|
|
n: Nested
|
|
|
|
navbar = Outer(n=Nested(number='1'))
|
|
assert isinstance(navbar.n, Nested)
|
|
assert navbar.n.number == 1
|
|
|
|
navbar = Outer(n=('2',))
|
|
assert isinstance(navbar.n, Nested)
|
|
assert navbar.n.number == 2
|
|
|
|
navbar = Outer(n={'number': '3'})
|
|
assert isinstance(navbar.n, Nested)
|
|
assert navbar.n.number == 3
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
Outer(n='not nested')
|
|
assert exc_info.value.errors() == [
|
|
{
|
|
'loc': ('n',),
|
|
'msg': 'instance of Nested, tuple or dict expected',
|
|
'type': 'type_error.dataclass',
|
|
'ctx': {'class_name': 'Nested'},
|
|
}
|
|
]
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
Outer(n=('x',))
|
|
assert exc_info.value.errors() == [
|
|
{'loc': ('n', 'number'), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}
|
|
]
|
|
|
|
|
|
def test_arbitrary_types_allowed():
|
|
@dataclasses.dataclass
|
|
class Button:
|
|
href: str
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
@pydantic.dataclasses.dataclass(config=Config)
|
|
class Navbar:
|
|
button: Button
|
|
|
|
btn = Button(href='a')
|
|
navbar = Navbar(button=btn)
|
|
assert navbar.button.href == 'a'
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
Navbar(button=('b',))
|
|
assert exc_info.value.errors() == [
|
|
{
|
|
'loc': ('button',),
|
|
'msg': 'instance of Button expected',
|
|
'type': 'type_error.arbitrary_type',
|
|
'ctx': {'expected_arbitrary_type': 'Button'},
|
|
}
|
|
]
|
|
|
|
|
|
def test_nested_dataclass_model():
|
|
@pydantic.dataclasses.dataclass
|
|
class Nested:
|
|
number: int
|
|
|
|
class Outer(BaseModel):
|
|
n: Nested
|
|
|
|
navbar = Outer(n=Nested(number='1'))
|
|
assert navbar.n.number == 1
|