数据类
如果您不想使用 pydantic 的 BaseModel
,您可以在标准 dataclasses 上获得相同的数据验证(在 Python 3.7 中引入) .
from datetime import datetime
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
signup_ts: datetime = None
user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
#> User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
(这个脚本是完整的,它应该“按原样”运行)
Note
请记住,pydantic.dataclasses.dataclass
是带验证的 dataclasses.dataclass
的直接替代品,不是 pydantic.BaseModel
的替代品(初始化挂钩的工作方式略有不同)。 在某些情况下,子类化 pydantic.BaseModel
是更好的选择。
有关更多信息和讨论,请参阅 pydantic/pydantic#710。
您可以使用所有标准 pydantic 字段类型,生成的数据类将与标准库dataclass
装饰器创建的数据类相同。
可以通过 __pydantic_model__
访问底层模型及其模式。 此外,需要 default_factory
的字段可以由 pydantic.Field
或 dataclasses.field
指定。
import dataclasses
from typing import List, Optional
from pydantic import Field
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: List[int] = dataclasses.field(default_factory=lambda: [0])
age: Optional[int] = dataclasses.field(
default=None,
metadata=dict(title='The age of the user', description='do not lie!')
)
height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)
user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
'title': 'User',
'type': 'object',
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {
'title': 'Name',
'default': 'John Doe',
'type': 'string',
},
'friends': {
'title': 'Friends',
'type': 'array',
'items': {'type': 'integer'},
},
'age': {
'title': 'The age of the user',
'description': 'do not lie!',
'type': 'integer',
},
'height': {
'title': 'The height in cm',
'minimum': 50,
'maximum': 300,
'type': 'integer',
},
},
'required': ['id'],
}
"""
import dataclasses
from typing import Optional
from pydantic import Field
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: list[int] = dataclasses.field(default_factory=lambda: [0])
age: Optional[int] = dataclasses.field(
default=None,
metadata=dict(title='The age of the user', description='do not lie!')
)
height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)
user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
'title': 'User',
'type': 'object',
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {
'title': 'Name',
'default': 'John Doe',
'type': 'string',
},
'friends': {
'title': 'Friends',
'type': 'array',
'items': {'type': 'integer'},
},
'age': {
'title': 'The age of the user',
'description': 'do not lie!',
'type': 'integer',
},
'height': {
'title': 'The height in cm',
'minimum': 50,
'maximum': 300,
'type': 'integer',
},
},
'required': ['id'],
}
"""
import dataclasses
from pydantic import Field
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: list[int] = dataclasses.field(default_factory=lambda: [0])
age: int | None = dataclasses.field(
default=None,
metadata=dict(title='The age of the user', description='do not lie!')
)
height: int | None = Field(None, title='The height in cm', ge=50, le=300)
user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
'title': 'User',
'type': 'object',
'properties': {
'id': {'title': 'Id', 'type': 'integer'},
'name': {
'title': 'Name',
'default': 'John Doe',
'type': 'string',
},
'friends': {
'title': 'Friends',
'type': 'array',
'items': {'type': 'integer'},
},
'age': {
'title': 'The age of the user',
'description': 'do not lie!',
'type': 'integer',
},
'height': {
'title': 'The height in cm',
'minimum': 50,
'maximum': 300,
'type': 'integer',
},
},
'required': ['id'],
}
"""
(这个脚本是完整的,它应该“按原样”运行)
pydantic.dataclasses.dataclass
的参数与标准装饰器相同,除了一个额外的关键字参数 config
与 Config 具有相同的含义。
Warning
在 v1.2 之后,必须安装 Mypy 插件 来类型检查 pydantic 数据类。
有关将验证器与数据类组合的更多信息,请参阅 数据类验证器。
数据类配置(Dataclass Config)¶
如果您想像修改 BaseModel
一样修改 Config
,您有以下三种选择:
from pydantic import ConfigDict
from pydantic.dataclasses import dataclass
# Option 1 - use directly a dict
# Note: `mypy` will still raise typo error
@dataclass(config=dict(validate_assignment=True))
class MyDataclass1:
a: int
# Option 2 - use `ConfigDict`
# (same as before at runtime since it's a `TypedDict` but with intellisense)
@dataclass(config=ConfigDict(validate_assignment=True))
class MyDataclass2:
a: int
# Option 3 - use a `Config` class like for a `BaseModel`
class Config:
validate_assignment = True
@dataclass(config=Config)
class MyDataclass3:
a: int
(这个脚本是完整的,它应该“按原样”运行)
Warning
在 v1.10 之后,pydantic 数据类支持 Config.extra
但 标准库 数据类的一些默认行为可能会占上风。 例如,当 print
时带有允许的额外字段的 pydantic 数据类时,它仍将使用 标准库 数据类的 __str__
方法并仅显示必需的字段。 未来可能会进一步改进这一点。
嵌套数据类(Nested dataclasses)¶
数据类和普通模型都支持嵌套数据类。
from pydantic import AnyUrl
from pydantic.dataclasses import dataclass
@dataclass
class NavbarButton:
href: AnyUrl
@dataclass
class Navbar:
button: NavbarButton
navbar = Navbar(button=('https://example.com',))
print(navbar)
#> Navbar(button=NavbarButton(href=AnyUrl('https://example.com', scheme='https',
#> host='example.com', tld='com', host_type='domain')))
(这个脚本是完整的,它应该“按原样”运行)
数据类属性可以由元组、字典或数据类本身的实例填充。
标准库数据类和_pydantic_数据类(Stdlib dataclasses and pydantic dataclasses)¶
转换标准库数据类为_pydantic_数据类(Convert stdlib dataclasses into pydantic dataclasses)¶
标准库 数据类(嵌套或非嵌套)只需用 pydantic.dataclasses.dataclass
装饰即可轻松转换为 pydantic 数据类。
Pydantic 将增强给定的 标准库 数据类,但不会改变默认行为(即未经验证)。
相反,它将围绕它创建一个包装器来触发验证,就像一个普通代理一样。
仍然可以通过 __dataclass__
属性访问 标准库 数据类(参见下面的示例)。
import dataclasses
from datetime import datetime
from typing import Optional
import pydantic
@dataclasses.dataclass
class Meta:
modified_date: Optional[datetime]
seen_count: int
@dataclasses.dataclass
class File(Meta):
filename: str
# `ValidatedFile` will be a proxy around `File`
ValidatedFile = pydantic.dataclasses.dataclass(File)
# the original dataclass is the `__dataclass__` attribute
assert ValidatedFile.__dataclass__ is File
validated_file = ValidatedFile(
filename=b'thefilename',
modified_date='2020-01-01T00:00',
seen_count='7',
)
print(validated_file)
#> File(modified_date=datetime.datetime(2020, 1, 1, 0, 0), seen_count=7,
#> filename='thefilename')
try:
ValidatedFile(
filename=['not', 'a', 'string'],
modified_date=None,
seen_count=3,
)
except pydantic.ValidationError as e:
print(e)
"""
1 validation error for File
filename
str type expected (type=type_error.str)
"""
# `File` is not altered and still does no validation by default
print(File(
#> File(modified_date=None, seen_count=3, filename=['not', 'a', 'string'])
filename=['not', 'a', 'string'],
modified_date=None,
seen_count=3,
))
import dataclasses
from datetime import datetime
import pydantic
@dataclasses.dataclass
class Meta:
modified_date: datetime | None
seen_count: int
@dataclasses.dataclass
class File(Meta):
filename: str
# `ValidatedFile` will be a proxy around `File`
ValidatedFile = pydantic.dataclasses.dataclass(File)
# the original dataclass is the `__dataclass__` attribute
assert ValidatedFile.__dataclass__ is File
validated_file = ValidatedFile(
filename=b'thefilename',
modified_date='2020-01-01T00:00',
seen_count='7',
)
print(validated_file)
#> File(modified_date=datetime.datetime(2020, 1, 1, 0, 0), seen_count=7,
#> filename='thefilename')
try:
ValidatedFile(
filename=['not', 'a', 'string'],
modified_date=None,
seen_count=3,
)
except pydantic.ValidationError as e:
print(e)
"""
1 validation error for File
filename
str type expected (type=type_error.str)
"""
# `File` is not altered and still does no validation by default
print(File(
#> File(modified_date=None, seen_count=3, filename=['not', 'a', 'string'])
filename=['not', 'a', 'string'],
modified_date=None,
seen_count=3,
))
(这个脚本是完整的,它应该“按原样”运行)
选择何时触发校验(Choose when to trigger validation)¶
一旦你的 标准库 数据类被 pydantic 数据类装饰器装饰,魔法方法就被添加来验证输入数据。 如果你愿意,你仍然可以继续使用你的数据类并选择何时触发它。
import dataclasses
from pydantic import ValidationError
from pydantic.dataclasses import dataclass as pydantic_dataclass, set_validation
@dataclasses.dataclass
class User:
id: int
name: str
# Enhance stdlib dataclass
pydantic_dataclass(User)
user1 = User(id='whatever', name='I want')
# validate data of `user1`
try:
user1.__pydantic_validate_values__()
except ValidationError as e:
print(e)
"""
1 validation error for User
id
value is not a valid integer (type=type_error.integer)
"""
# Enforce validation
try:
with set_validation(User, True):
User(id='whatever', name='I want')
except ValidationError as e:
print(e)
"""
1 validation error for User
id
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
从标准库数据类继承(Inherit from stdlib dataclasses)¶
标准库 数据类(嵌套或非嵌套)也可以被继承,pydantic 将自动验证所有继承的字段。
import dataclasses
import pydantic
@dataclasses.dataclass
class Z:
z: int
@dataclasses.dataclass
class Y(Z):
y: int = 0
@pydantic.dataclasses.dataclass
class X(Y):
x: int = 0
foo = X(x=b'1', y='2', z='3')
print(foo)
#> X(z=3, y=2, x=1)
try:
X(z='pika')
except pydantic.ValidationError as e:
print(e)
"""
1 validation error for X
z
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
将标准库数据类与 BaseModel
一起使用(Use of stdlib dataclasses with BaseModel
)¶
请记住,标准库 数据类(嵌套或非嵌套)在与 BaseModel
混合时会自动转换为_pydantic_数据类! 此外,生成的 pydantic 数据类将与原始配置具有完全相同的配置(order
、frozen
、...)。
import dataclasses
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ValidationError
@dataclasses.dataclass(frozen=True)
class User:
name: str
@dataclasses.dataclass
class File:
filename: str
last_modification_time: Optional[datetime] = None
class Foo(BaseModel):
file: File
user: Optional[User] = None
file = File(
filename=['not', 'a', 'string'],
last_modification_time='2020-01-01T00:00',
) # nothing is validated as expected
print(file)
#> File(filename=['not', 'a', 'string'],
#> last_modification_time='2020-01-01T00:00')
try:
Foo(file=file)
except ValidationError as e:
print(e)
"""
1 validation error for Foo
file -> filename
str type expected (type=type_error.str)
"""
foo = Foo(file=File(filename='myfile'), user=User(name='pika'))
try:
foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
print(e)
#> cannot assign to field 'name'
import dataclasses
from datetime import datetime
from pydantic import BaseModel, ValidationError
@dataclasses.dataclass(frozen=True)
class User:
name: str
@dataclasses.dataclass
class File:
filename: str
last_modification_time: datetime | None = None
class Foo(BaseModel):
file: File
user: User | None = None
file = File(
filename=['not', 'a', 'string'],
last_modification_time='2020-01-01T00:00',
) # nothing is validated as expected
print(file)
#> File(filename=['not', 'a', 'string'],
#> last_modification_time='2020-01-01T00:00')
try:
Foo(file=file)
except ValidationError as e:
print(e)
"""
1 validation error for Foo
file -> filename
str type expected (type=type_error.str)
"""
foo = Foo(file=File(filename='myfile'), user=User(name='pika'))
try:
foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
print(e)
#> cannot assign to field 'name'
(这个脚本是完整的,它应该“按原样”运行)
使用自定义类型(Use custom types)¶
由于 标准库 数据类会自动转换为使用自定义类型添加验证,因此可能会导致一些意外行为。 在这种情况下,您只需在配置中添加 arbitrary_types_allowed
即可!
import dataclasses
import pydantic
class ArbitraryType:
def __init__(self, value):
self.value = value
def __repr__(self):
return f'ArbitraryType(value={self.value!r})'
@dataclasses.dataclass
class DC:
a: ArbitraryType
b: str
# valid as it is a builtin dataclass without validation
my_dc = DC(a=ArbitraryType(value=3), b='qwe')
try:
class Model(pydantic.BaseModel):
dc: DC
other: str
Model(dc=my_dc, other='other')
except RuntimeError as e: # invalid as it is now a pydantic dataclass
print(e)
"""
no validator found for <class
'dataclasses_arbitrary_types_allowed.ArbitraryType'>, see
`arbitrary_types_allowed` in Config
"""
class Model(pydantic.BaseModel):
dc: DC
other: str
class Config:
arbitrary_types_allowed = True
m = Model(dc=my_dc, other='other')
print(repr(m))
#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')
(这个脚本是完整的,它应该“按原样”运行)
初始化钩子(Initialize hooks)¶
初始化数据类时,可以在 __post_init_post_parse__
的帮助下在 验证后 执行代码。 这与 __post_init__
不同,它在验证之前执行代码。
Tip
如果您使用 标准库 dataclass
,您可能只有 __post_init__
可用,并希望在之前完成验证。 在这种情况下,您可以设置 Config.post_init_call = 'after_validation'
from pydantic.dataclasses import dataclass
@dataclass
class Birth:
year: int
month: int
day: int
@dataclass
class User:
birth: Birth
def __post_init__(self):
print(self.birth)
#> {'year': 1995, 'month': 3, 'day': 2}
def __post_init_post_parse__(self):
print(self.birth)
#> Birth(year=1995, month=3, day=2)
user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})
(这个脚本是完整的,它应该“按原样”运行)
从版本 v1.0 开始,任何用 dataclasses.InitVar
注释的字段都会传递给 __post_init__
和 __post_init_post_parse__
。
from dataclasses import InitVar
from pathlib import Path
from typing import Optional
from pydantic.dataclasses import dataclass
@dataclass
class PathData:
path: Path
base_path: InitVar[Optional[Path]]
def __post_init__(self, base_path):
print(f'Received path={self.path!r}, base_path={base_path!r}')
#> Received path='world', base_path='/hello'
def __post_init_post_parse__(self, base_path):
if base_path is not None:
self.path = base_path / self.path
path_data = PathData('world', base_path='/hello')
# Received path='world', base_path='/hello'
assert path_data.path == Path('/hello/world')
from dataclasses import InitVar
from pathlib import Path
from pydantic.dataclasses import dataclass
@dataclass
class PathData:
path: Path
base_path: InitVar[Path | None]
def __post_init__(self, base_path):
print(f'Received path={self.path!r}, base_path={base_path!r}')
#> Received path='world', base_path='/hello'
def __post_init_post_parse__(self, base_path):
if base_path is not None:
self.path = base_path / self.path
path_data = PathData('world', base_path='/hello')
# Received path='world', base_path='/hello'
assert path_data.path == Path('/hello/world')
(这个脚本是完整的,它应该“按原样”运行)
与标准库数据类的区别(Difference with stdlib dataclasses)¶
请注意,Python 标准库中的dataclasses.dataclass
仅实现了__post_init__
方法,因为它不运行验证步骤。
当用 pydantic.dataclasses.dataclass
替换 dataclasses.dataclass
的用法时,建议将 __post_init__
方法中执行的代码移动到 __post_init_post_parse__
方法中,只留下需要的部分代码 在验证之前执行。
JSON Dumping¶
Pydantic 数据类没有 .json()
函数。 要将它们转储为 JSON,您需要按如下方式使用 pydantic_encoder
:
import dataclasses
import json
from typing import List
from pydantic.dataclasses import dataclass
from pydantic.json import pydantic_encoder
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: List[int] = dataclasses.field(default_factory=lambda: [0])
user = User(id='42')
print(json.dumps(user, indent=4, default=pydantic_encoder))
"""
{
"id": 42,
"name": "John Doe",
"friends": [
0
]
}
"""
import dataclasses
import json
from pydantic.dataclasses import dataclass
from pydantic.json import pydantic_encoder
@dataclass
class User:
id: int
name: str = 'John Doe'
friends: list[int] = dataclasses.field(default_factory=lambda: [0])
user = User(id='42')
print(json.dumps(user, indent=4, default=pydantic_encoder))
"""
{
"id": 42,
"name": "John Doe",
"friends": [
0
]
}
"""
(这个脚本是完整的,它应该“按原样”运行)