模型
在pydantic中定义对象的主要方法是通过模型(模型只是继承自的类BaseModel
)。
您可以将模型视为类似于严格类型化语言中的类型,或者视为 API 中单个端点的要求。
不受信任的数据可以传递给模型,在解析和验证之后,pydantic
保证生成的模型实例的字段将符合模型上定义的字段类型。
笔记
pydantic主要是一个解析库,而不是一个验证库。
验证是达到目的的一种手段:建立一个符合所提供的类型和约束的模型。
换句话说,pydantic保证输出模型的类型和约束,而不是输入数据。
这听起来像是一个深奥的区别,但事实并非如此。如果您不确定这意味着什么或它如何影响您的使用,您应该阅读下面有关数据转换的部分。
虽然验证不是pydantic的主要目的,但您可以使用此库进行自定义验证。
基本模型使用(Basic model usage)¶
from pydantic import BaseModel
class User(BaseModel):
id: int
name = 'Jane Doe'
这儿的 User
是一个有两个字段的模型,id
一个是整数,是必需的,另一个name
是字符串,不是必需的(它有一个默认值)。name
的类型是从默认值推断出来的,因此不需要类型注释(但是当某些字段没有类型注释时请注意有关字段顺序的警告)。
user = User(id='123')
user_x = User(id='123.45')
这儿的 user
是 User
的实例,对象的初始化将执行所有的解析和验证。如果没有引发ValidationError
,则生成的模型实例是有效的。
assert user.id == 123
assert user_x.id == 123
assert isinstance(user_x.id, int) # Note that 123.45 was casted to an int and its value is 123
有关user_x
形式的数据转换的更多详细信息,请参阅数据转换。
模型的字段可以作为用户对象的普通属性来访问。 根据字段类型,字符串“123”已转换为 int
assert user.name == 'Jane Doe'
name
在用户初始化时没有设置,所以它有默认值
assert user.__fields_set__ == {'id'}
初始化用户时提供的字段。
assert user.dict() == dict(user) == {'id': 123, 'name': 'Jane Doe'}
.dict()
或 dict(user)
将提供字段的字典,但 .dict()
可以接受许多其他参数。
user.id = 321
assert user.id == 321
该模型是可变的,因此可以更改字段值。
模型属性(Model properties)¶
上面的例子只展示了模型可以做什么的冰山一角。模型具有以下方法和属性:
dict()
- 返回模型字段和值的字典形式; 参考 导出模型
json()
- 返回一个 JSON 字符串表示
dict()
; 参考 导出模型 copy()
- 返回模型的副本(默认情况下为浅副本); 参考 导出模型
parse_obj()
- 如果对象不是字典,则用于将任何对象加载到模型中并进行错误处理的实用程序; 参考 辅助函数
parse_raw()
- 用于加载多种格式的字符串的辅助函数; 参考 [辅助函数] (#helper-functions)
parse_file()
- 类似于
parse_raw()
但用于文件路径; 参考 [辅助函数] (#helper-functions) from_orm()
- 将数据从任意类加载到模型中; 参考 ORM模式
schema()
- 返回将模型表示为 JSON Schema 的字典; 参考 图式
schema_json()
- 返回
schema()
的 JSON 字符串表示; 参考 图式 construct()
- 一种无需运行验证即可创建模型的类方法; 参考 创建无需校验的模型
__fields_set__
- 初始化模型实例时设置的字段名称集
__fields__
- 模型字段的字典
__config__
- 模型的配置类, 参考 模型配置
嵌套模型(Recursive Models)¶
可以使用模型本身作为注释中的类型来定义更复杂的分层数据结构。
from typing import List, Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple = 'x'
banana = 'y'
class Spam(BaseModel):
foo: Foo
bars: List[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
#> foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'),
#> Bar(apple='x2', banana='y')]
print(m.dict())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [
{'apple': 'x1', 'banana': 'y'},
{'apple': 'x2', 'banana': 'y'},
],
}
"""
from typing import Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple = 'x'
banana = 'y'
class Spam(BaseModel):
foo: Foo
bars: list[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
#> foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'),
#> Bar(apple='x2', banana='y')]
print(m.dict())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [
{'apple': 'x1', 'banana': 'y'},
{'apple': 'x2', 'banana': 'y'},
],
}
"""
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: float | None = None
class Bar(BaseModel):
apple = 'x'
banana = 'y'
class Spam(BaseModel):
foo: Foo
bars: list[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
#> foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'),
#> Bar(apple='x2', banana='y')]
print(m.dict())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [
{'apple': 'x1', 'banana': 'y'},
{'apple': 'x2', 'banana': 'y'},
],
}
"""
(这个脚本是完整的,它应该“按原样”运行)
对于自引用模型, 见 延时注解.
ORM 模式【又名任意类实例】 (aka Arbitrary Class Instances)¶
可以从任意类实例创建 Pydantic 模型以支持映射到 ORM 对象的模型。
去做这个:
- Config 的属性
orm_mode
必须设置为True
. - 必须使用特殊构造函数
from_orm
来创建模型实例。
此处的示例使用 SQLAlchemy,但同样的方法适用于任何 ORM。
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: List[constr(max_length=255)]
class Config:
orm_mode = True
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode.CompanyOrm object at 0x7fad449d9050>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: list[constr(max_length=255)]
class Config:
orm_mode = True
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode_3_9.CompanyOrm object at 0x7fad44bcead0>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']
(这个脚本是完整的,它应该“按原样”运行)
保留名称(Reserved names)¶
您可能希望在保留的 SQLAlchemy 字段之后重新命名。 在这种情况下,Field 别名会很方便:
import typing
from pydantic import BaseModel, Field
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
class MyModel(BaseModel):
metadata: typing.Dict[str, str] = Field(alias='metadata_')
class Config:
orm_mode = True
Base = declarative_base()
class SQLModel(Base):
__tablename__ = 'my_table'
id = sa.Column('id', sa.Integer, primary_key=True)
# 'metadata' is reserved by SQLAlchemy, hence the '_'
metadata_ = sa.Column('metadata', sa.JSON)
sql_model = SQLModel(metadata_={'key': 'val'}, id=1)
pydantic_model = MyModel.from_orm(sql_model)
print(pydantic_model.dict())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.dict(by_alias=True))
#> {'metadata_': {'key': 'val'}}
from pydantic import BaseModel, Field
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
class MyModel(BaseModel):
metadata: dict[str, str] = Field(alias='metadata_')
class Config:
orm_mode = True
Base = declarative_base()
class SQLModel(Base):
__tablename__ = 'my_table'
id = sa.Column('id', sa.Integer, primary_key=True)
# 'metadata' is reserved by SQLAlchemy, hence the '_'
metadata_ = sa.Column('metadata', sa.JSON)
sql_model = SQLModel(metadata_={'key': 'val'}, id=1)
pydantic_model = MyModel.from_orm(sql_model)
print(pydantic_model.dict())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.dict(by_alias=True))
#> {'metadata_': {'key': 'val'}}
(这个脚本是完整的,它应该“按原样”运行)
Note
上面的示例之所以有效,是因为别名优先于字段填充的字段名称。 访问 SQLModel
的 metadata
属性会导致ValidationError
。
嵌套ORM模型(Recursive ORM models)¶
ORM 实例将使用 from_orm
递归地以及在顶层进行解析。
这里使用普通类来演示原理,但也可以使用任何 ORM 类。
from typing import List
from pydantic import BaseModel
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species
class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
self.name = name
self.age = age
self.pets = pets
class Pet(BaseModel):
name: str
species: str
class Config:
orm_mode = True
class Person(BaseModel):
name: str
age: float = None
pets: List[Pet]
class Config:
orm_mode = True
bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.from_orm(anna)
print(anna_model)
#> name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'),
#> Pet(name='Orion', species='cat')]
from pydantic import BaseModel
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species
class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: list[PetCls]):
self.name = name
self.age = age
self.pets = pets
class Pet(BaseModel):
name: str
species: str
class Config:
orm_mode = True
class Person(BaseModel):
name: str
age: float = None
pets: list[Pet]
class Config:
orm_mode = True
bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.from_orm(anna)
print(anna_model)
#> name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'),
#> Pet(name='Orion', species='cat')]
(这个脚本是完整的,它应该“按原样”运行)
数据绑定(Data binding)¶
pydantic 使用 GetterDict
类处理任意类(参见 utils.py), 它试图为任何类提供类似字典的接口。 您可以通过将您自己的 GetterDict
子类设置为 Config.getter_dict
的值来自定义其工作方式(参见 config)。
您还可以使用带有 pre=True
的 root_validators 自定义类验证。
在这种情况下,您的验证器函数将被传递给您可以复制和修改的 GetterDict
实例。
将为每个字段调用 GetterDict
实例,并将标记(如果未设置其他默认值)。 返回此标记意味着该字段丢失。 任何其他值都将被解释为该字段的值。
from pydantic import BaseModel
from typing import Any, Optional
from pydantic.utils import GetterDict
from xml.etree.ElementTree import fromstring
xmlstring = """
<User Id="2138">
<FirstName />
<LoggedIn Value="true" />
</User>
"""
class UserGetter(GetterDict):
def get(self, key: str, default: Any) -> Any:
# element attributes
if key in {'Id', 'Status'}:
return self._obj.attrib.get(key, default)
# element children
else:
try:
return self._obj.find(key).attrib['Value']
except (AttributeError, KeyError):
return default
class User(BaseModel):
Id: int
Status: Optional[str]
FirstName: Optional[str]
LastName: Optional[str]
LoggedIn: bool
class Config:
orm_mode = True
getter_dict = UserGetter
user = User.from_orm(fromstring(xmlstring))
from pydantic import BaseModel
from typing import Any
from pydantic.utils import GetterDict
from xml.etree.ElementTree import fromstring
xmlstring = """
<User Id="2138">
<FirstName />
<LoggedIn Value="true" />
</User>
"""
class UserGetter(GetterDict):
def get(self, key: str, default: Any) -> Any:
# element attributes
if key in {'Id', 'Status'}:
return self._obj.attrib.get(key, default)
# element children
else:
try:
return self._obj.find(key).attrib['Value']
except (AttributeError, KeyError):
return default
class User(BaseModel):
Id: int
Status: str | None
FirstName: str | None
LastName: str | None
LoggedIn: bool
class Config:
orm_mode = True
getter_dict = UserGetter
user = User.from_orm(fromstring(xmlstring))
(这个脚本是完整的,它应该“按原样”运行)
错误处理(Error Handling)¶
pydantic 会在发现正在验证的数据中存在错误时引发 ValidationError
。
Note
验证代码不应引发 ValidationError
本身,而是引发 ValueError
、TypeError
或 AssertionError
(或 ValueError
或 TypeError
的子类),它们将被捕获并用于填充 ValidationError
。
无论发现多少错误,都会引发一个异常,即 ValidationError
将包含有关所有错误及其发生方式的信息。
您可以通过多种方式访问这些错误:
e.errors()
- 方法将返回在输入数据中发现的错误列表。
e.json()
- 方法将返回
errors
的 JSON 表示。 str(e)
- 方法将返回错误的人类可读表示。
每个错误对象包含:
loc
- 错误的位置作为列表。 列表中的第一项将是发生错误的字段,如果该字段是 子模块,则将出现后续项以指示错误的嵌套位置。
type
- 错误类型的计算机可读标识符。
msg
:错误类型的计算机可读标识符。
ctx
- 一个可选对象,其中包含呈现错误消息所需的值。
作为示范:
from typing import List
from pydantic import BaseModel, ValidationError, conint
class Location(BaseModel):
lat = 0.1
lng = 10.1
class Model(BaseModel):
is_required: float
gt_int: conint(gt=42)
list_of_ints: List[int] = None
a_float: float = None
recursive_model: Location = None
data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
recursive_model={'lat': 4.2, 'lng': 'New York'},
gt_int=21,
)
try:
Model(**data)
except ValidationError as e:
print(e)
"""
5 validation errors for Model
is_required
field required (type=value_error.missing)
gt_int
ensure this value is greater than 42 (type=value_error.number.not_gt;
limit_value=42)
list_of_ints -> 2
value is not a valid integer (type=type_error.integer)
a_float
value is not a valid float (type=type_error.float)
recursive_model -> lng
value is not a valid float (type=type_error.float)
"""
try:
Model(**data)
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"is_required"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"gt_int"
],
"msg": "ensure this value is greater than 42",
"type": "value_error.number.not_gt",
"ctx": {
"limit_value": 42
}
},
{
"loc": [
"list_of_ints",
2
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"a_float"
],
"msg": "value is not a valid float",
"type": "type_error.float"
},
{
"loc": [
"recursive_model",
"lng"
],
"msg": "value is not a valid float",
"type": "type_error.float"
}
]
"""
from pydantic import BaseModel, ValidationError, conint
class Location(BaseModel):
lat = 0.1
lng = 10.1
class Model(BaseModel):
is_required: float
gt_int: conint(gt=42)
list_of_ints: list[int] = None
a_float: float = None
recursive_model: Location = None
data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
recursive_model={'lat': 4.2, 'lng': 'New York'},
gt_int=21,
)
try:
Model(**data)
except ValidationError as e:
print(e)
"""
5 validation errors for Model
is_required
field required (type=value_error.missing)
gt_int
ensure this value is greater than 42 (type=value_error.number.not_gt;
limit_value=42)
list_of_ints -> 2
value is not a valid integer (type=type_error.integer)
a_float
value is not a valid float (type=type_error.float)
recursive_model -> lng
value is not a valid float (type=type_error.float)
"""
try:
Model(**data)
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"is_required"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"gt_int"
],
"msg": "ensure this value is greater than 42",
"type": "value_error.number.not_gt",
"ctx": {
"limit_value": 42
}
},
{
"loc": [
"list_of_ints",
2
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"a_float"
],
"msg": "value is not a valid float",
"type": "type_error.float"
},
{
"loc": [
"recursive_model",
"lng"
],
"msg": "value is not a valid float",
"type": "type_error.float"
}
]
"""
(这个脚本是完整的,它应该“按原样”运行)
自定义错误(Custom Errors)¶
在您的自定义数据类型或验证器中,您应该使用 ValueError
、TypeError
或 AssertionError
来引发错误。
有关使用 @validator
装饰器的更多详细信息,请参阅 校验器。
from pydantic import BaseModel, ValidationError, validator
class Model(BaseModel):
foo: str
@validator('foo')
def value_must_equal_bar(cls, v):
if v != 'bar':
raise ValueError('value must be "bar"')
return v
try:
Model(foo='ber')
except ValidationError as e:
print(e.errors())
"""
[
{
'loc': ('foo',),
'msg': 'value must be "bar"',
'type': 'value_error',
},
]
"""
(这个脚本是完整的,它应该“按原样”运行)
您还可以定义自己的错误类,它可以自定义错误代码、消息模板和上下文:
from pydantic import BaseModel, PydanticValueError, ValidationError, validator
class NotABarError(PydanticValueError):
code = 'not_a_bar'
msg_template = 'value is not "bar", got "{wrong_value}"'
class Model(BaseModel):
foo: str
@validator('foo')
def value_must_equal_bar(cls, v):
if v != 'bar':
raise NotABarError(wrong_value=v)
return v
try:
Model(foo='ber')
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"foo"
],
"msg": "value is not \"bar\", got \"ber\"",
"type": "value_error.not_a_bar",
"ctx": {
"wrong_value": "ber"
}
}
]
"""
(这个脚本是完整的,它应该“按原样”运行)
辅助函数(Helper Functions)¶
Pydantic 在模型上提供了三个 classmethod
辅助函数来解析数据:
parse_obj
: 这与模型的__init__
方法非常相似,除了它采用字典而不是关键字参数。 如果传递的对象不是字典,则会引发ValidationError
。parse_raw
: 这需要 str 或 bytes 并将其解析为 json,然后将结果传递给parse_obj
。通过适当设置content_type
参数也支持解析 pickle 数据。parse_file
: 这需要一个文件路径,读取文件并将内容传递给parse_raw
。 如果省略了content_type
,则从文件的扩展名中推断出来。
import pickle
from datetime import datetime
from pathlib import Path
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: datetime = None
m = User.parse_obj({'id': 123, 'name': 'James'})
print(m)
#> id=123 signup_ts=None name='James'
try:
User.parse_obj(['not', 'a', 'dict'])
except ValidationError as e:
print(e)
"""
1 validation error for User
__root__
User expected dict not list (type=type_error)
"""
# assumes json as no content type passed
m = User.parse_raw('{"id": 123, "name": "James"}')
print(m)
#> id=123 signup_ts=None name='James'
pickle_data = pickle.dumps({
'id': 123,
'name': 'James',
'signup_ts': datetime(2017, 7, 14)
})
m = User.parse_raw(
pickle_data, content_type='application/pickle', allow_pickle=True
)
print(m)
#> id=123 signup_ts=datetime.datetime(2017, 7, 14, 0, 0) name='James'
path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)
#> id=123 signup_ts=None name='James'
(这个脚本是完整的,它应该“按原样”运行)
Warning
引用 官方 pickle
文档,“pickle 模块对于错误或恶意构造的数据不安全。切勿取消接收来自不受信任或未经身份验证的来源。”
Info
因为它会导致任意代码执行,作为安全措施,您需要显式地将 allow_pickle
传递给解析函数,以便加载 pickle
数据。
创建无需校验的模型(Creating models without validation)¶
pydantic 还提供了 construct()
方法,该方法允许创建模型无需验证当数据已经过验证或来自受信任的来源并且您希望尽可能高效地创建模型时,这可能很有用 可能(construct()
通常比创建具有完整验证的模型快 30 倍左右)。
Warning
construct()
不做任何验证,这意味着它可以创建无效的模型。 您应该只对已经过验证或您信任的数据使用 construct()
方法。
from pydantic import BaseModel
class User(BaseModel):
id: int
age: int
name: str = 'John Doe'
original_user = User(id=123, age=32)
user_data = original_user.dict()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.__fields_set__
print(fields_set)
#> {'id', 'age'}
# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...
# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.__fields_set__)
#> {'id', 'age'}
# construct can be dangerous, only use it with validated data!:
bad_user = User.construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')
(这个脚本是完整的,它应该“按原样”运行)
construct()
的 _fields_set
关键字参数是可选的,但可以让您更准确地了解哪些字段是最初设置的,哪些不是。 如果它被省略,__fields_set__
将只是所提供数据的键。
例如,在上面的示例中,如果未提供 _fields_set
,则new_user.fields_set
将为{'id', 'age', 'name'}
。
通用模型(Generic Models)¶
Pydantic 支持创建通用模型,以便更轻松地重用通用模型结构。
为了声明通用模型,执行以下步骤:
- 声明一个或多个
typing.TypeVar
实例以用于参数化您的模型。 - 声明一个继承自
pydantic.generics.GenericModel
和typing.Generic
的 pydantic 模型,在其中将TypeVar
实例作为参数传递给typing.Generic
。 - 使用
TypeVar
实例作为注解,您可以在其中将它们替换为其他类型或 pydantic 模型。
下面是一个使用 GenericModel
创建易于重用的 HTTP 响应负载包装器的示例:
from typing import Generic, TypeVar, Optional, List
from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel
DataT = TypeVar('DataT')
class Error(BaseModel):
code: int
message: str
class DataModel(BaseModel):
numbers: List[int]
people: List[str]
class Response(GenericModel, Generic[DataT]):
data: Optional[DataT]
error: Optional[Error]
@validator('error', always=True)
def check_consistency(cls, v, values):
if v is not None and values['data'] is not None:
raise ValueError('must not provide both data and error')
if v is None and values.get('data') is None:
raise ValueError('must provide data or error')
return v
data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message='Not found')
print(Response[int](data=1))
#> data=1 error=None
print(Response[str](data='value'))
#> data='value' error=None
print(Response[str](data='value').dict())
#> {'data': 'value', 'error': None}
print(Response[DataModel](data=data).dict())
"""
{
'data': {'numbers': [1, 2, 3], 'people': []},
'error': None,
}
"""
print(Response[DataModel](error=error).dict())
"""
{
'data': None,
'error': {'code': 404, 'message': 'Not found'},
}
"""
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
2 validation errors for Response[int]
data
value is not a valid integer (type=type_error.integer)
error
must provide data or error (type=value_error)
"""
from typing import Generic, TypeVar, Optional
from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel
DataT = TypeVar('DataT')
class Error(BaseModel):
code: int
message: str
class DataModel(BaseModel):
numbers: list[int]
people: list[str]
class Response(GenericModel, Generic[DataT]):
data: Optional[DataT]
error: Optional[Error]
@validator('error', always=True)
def check_consistency(cls, v, values):
if v is not None and values['data'] is not None:
raise ValueError('must not provide both data and error')
if v is None and values.get('data') is None:
raise ValueError('must provide data or error')
return v
data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message='Not found')
print(Response[int](data=1))
#> data=1 error=None
print(Response[str](data='value'))
#> data='value' error=None
print(Response[str](data='value').dict())
#> {'data': 'value', 'error': None}
print(Response[DataModel](data=data).dict())
"""
{
'data': {'numbers': [1, 2, 3], 'people': []},
'error': None,
}
"""
print(Response[DataModel](error=error).dict())
"""
{
'data': None,
'error': {'code': 404, 'message': 'Not found'},
}
"""
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
2 validation errors for Response[int]
data
value is not a valid integer (type=type_error.integer)
error
must provide data or error (type=value_error)
"""
from typing import Generic, TypeVar
from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel
DataT = TypeVar('DataT')
class Error(BaseModel):
code: int
message: str
class DataModel(BaseModel):
numbers: list[int]
people: list[str]
class Response(GenericModel, Generic[DataT]):
data: DataT | None
error: Error | None
@validator('error', always=True)
def check_consistency(cls, v, values):
if v is not None and values['data'] is not None:
raise ValueError('must not provide both data and error')
if v is None and values.get('data') is None:
raise ValueError('must provide data or error')
return v
data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message='Not found')
print(Response[int](data=1))
#> data=1 error=None
print(Response[str](data='value'))
#> data='value' error=None
print(Response[str](data='value').dict())
#> {'data': 'value', 'error': None}
print(Response[DataModel](data=data).dict())
"""
{
'data': {'numbers': [1, 2, 3], 'people': []},
'error': None,
}
"""
print(Response[DataModel](error=error).dict())
"""
{
'data': None,
'error': {'code': 404, 'message': 'Not found'},
}
"""
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
2 validation errors for Response[int]
data
value is not a valid integer (type=type_error.integer)
error
must provide data or error (type=value_error)
"""
(这个脚本是完整的,它应该“按原样”运行)
如果您在通用模型定义中设置 Config
或使用 validator
,它将以与从 BaseModel
继承时相同的方式应用于具体子类。 在泛型类上定义的任何方法也将被继承。
Pydantic 的泛型也与 mypy 正确集成,因此如果您要在不使用 GenericModel
的情况下声明类型,您将获得您希望 mypy 提供的所有类型检查。
Note
在内部,pydantic 使用 create_model
在运行时生成(缓存的)具体 BaseModel
,因此使用 GenericModel
引入的开销基本上为零。
要从 GenericModel
继承而不替换 TypeVar
实例,类还必须从 typing.Generic
继承:
from typing import TypeVar, Generic
from pydantic.generics import GenericModel
TypeX = TypeVar('TypeX')
class BaseClass(GenericModel, Generic[TypeX]):
X: TypeX
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
# Inherit from Generic[TypeX]
pass
# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1
(这个脚本是完整的,它应该“按原样”运行)
您还可以创建 GenericModel
的通用子类,部分或完全替换超类中的类型参数。
from typing import TypeVar, Generic
from pydantic.generics import GenericModel
TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')
class BaseClass(GenericModel, Generic[TypeX, TypeY]):
x: TypeX
y: TypeY
class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
z: TypeZ
# Replace TypeY by str
print(ChildClass[str, int](x=1, y='y', z=3))
#> x=1 y='y' z=3
(这个脚本是完整的,它应该“按原样”运行)
如果具体子类的名称很重要,您还可以覆盖默认行为:
from typing import Generic, TypeVar, Type, Any, Tuple
from pydantic.generics import GenericModel
DataT = TypeVar('DataT')
class Response(GenericModel, Generic[DataT]):
data: DataT
@classmethod
def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
return f'{params[0].__name__.title()}Response'
print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')
from typing import Generic, TypeVar, Any
from pydantic.generics import GenericModel
DataT = TypeVar('DataT')
class Response(GenericModel, Generic[DataT]):
data: DataT
@classmethod
def __concrete_name__(cls: type[Any], params: tuple[type[Any], ...]) -> str:
return f'{params[0].__name__.title()}Response'
print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')
(这个脚本是完整的,它应该“按原样”运行)
在嵌套模型中使用相同的 TypeVar
允许您在模型的不同点强制执行类型关系:
from typing import Generic, TypeVar
from pydantic import ValidationError
from pydantic.generics import GenericModel
T = TypeVar('T')
class InnerT(GenericModel, Generic[T]):
inner: T
class OuterT(GenericModel, Generic[T]):
outer: T
nested: InnerT[T]
nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
nested = InnerT[str](inner='a')
print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
print(e)
"""
2 validation errors for OuterT[int]
outer
value is not a valid integer (type=type_error.integer)
nested -> inner
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
Pydantic 还像处理 List
和 Dict
等内置泛型类型一样处理 GenericModel
,以使其保持未参数化或使用有界 TypeVar
实例:
- 如果您在实例化通用模型之前没有指定参数,它们将被视为
Any
- 您可以使用一个或多个bounded(有界)参数对模型进行参数化以添加子类检查
此外,与 List
和 Dict
一样,使用 TypeVar
指定的任何参数稍后都可以替换为具体类型。
from typing import Generic, TypeVar
from pydantic import ValidationError
from pydantic.generics import GenericModel
AT = TypeVar('AT')
BT = TypeVar('BT')
class Model(GenericModel, Generic[AT, BT]):
a: AT
b: BT
print(Model(a='a', b='a'))
#> a='a' b='a'
IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
typevar_model(a='a', b='a')
except ValidationError as exc:
print(exc)
"""
2 validation errors for Model[int, IntT]
a
value is not a valid integer (type=type_error.integer)
b
value is not a valid integer (type=type_error.integer)
"""
concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1
(这个脚本是完整的,它应该“按原样”运行)
动态模型的创建(Dynamic model creation)¶
在某些情况下,直到运行时才知道模型的形态(shape)。 为此 pydantic 提供了 create_model
方法来允许动态创建模型。
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model('DynamicFoobarModel', foo=(str, ...), bar=123)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
(这个脚本是完整的,它应该“按原样”运行)
这里的 StaticFoobarModel
和 DynamicFoobarModel
是相同的。
Warning
请参阅 必须的可选参数 中的注释,以了解省略号作为字段默认值和仅注释字段之间的区别。 参见 pydantic/pydantic#1047 获取更多详细信息。
字段由 (<type>, <default value>)
形式的元组或仅由默认值定义。 特殊关键字参数 __config__
和 __base__
可用于自定义新模型。 这包括使用额外字段扩展基本模型。
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 123
BarModel = create_model(
'BarModel',
apple='russet',
banana='yellow',
__base__=FooModel,
)
print(BarModel)
#> <class 'pydantic.main.BarModel'>
print(BarModel.__fields__.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])
(这个脚本是完整的,它应该“按原样”运行)
您还可以通过将字典传递给 __validators__
参数来添加验证器。
from pydantic import create_model, ValidationError, validator
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {
'username_validator':
validator('username')(username_alphanumeric)
}
UserModel = create_model(
'UserModel',
username=(str, ...),
__validators__=validators
)
user = UserModel(username='scolvin')
print(user)
#> username='scolvin'
try:
UserModel(username='scolvi%n')
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
username
must be alphanumeric (type=assertion_error)
"""
(这个脚本是完整的,它应该“按原样”运行)
从NamedTuple
或TypedDict
创建模型(Model creation from NamedTuple
or TypedDict
)¶
有时,您已经在应用程序中使用了继承自 NamedTuple
或 TypedDict
的类,并且您不想复制所有信息以拥有 BaseModel
。 为此pydantic 提供了create_model_from_namedtuple
和create_model_from_typeddict
方法。 这些方法具有与 create_model
完全相同的关键字参数。
from typing_extensions import TypedDict
from pydantic import ValidationError, create_model_from_typeddict
class User(TypedDict):
name: str
id: int
class Config:
extra = 'forbid'
UserM = create_model_from_typeddict(User, __config__=Config)
print(repr(UserM(name=123, id='3')))
#> User(name='123', id=3)
try:
UserM(name=123, id='3', other='no')
except ValidationError as e:
print(e)
"""
1 validation error for User
other
extra fields not permitted (type=value_error.extra)
"""
(这个脚本是完整的,它应该“按原样”运行)
自定义根类型(Custom Root Types)¶
Pydantic 模型可以通过声明 __root__
字段来定义自定义根类型。
根类型可以是 pydantic 支持的任何类型,并由 __root__
字段上的类型提示指定。 根值可以通过 __root__
关键字参数传递给模型 __init__
,或者作为 parse_obj
的第一个也是唯一一个参数。
from typing import List
import json
from pydantic import BaseModel
from pydantic.schema import schema
class Pets(BaseModel):
__root__: List[str]
print(Pets(__root__=['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets(__root__=['dog', 'cat']).json())
#> ["dog", "cat"]
print(Pets.parse_obj(['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets.schema())
"""
{
'title': 'Pets',
'type': 'array',
'items': {'type': 'string'},
}
"""
pets_schema = schema([Pets])
print(json.dumps(pets_schema, indent=2))
"""
{
"definitions": {
"Pets": {
"title": "Pets",
"type": "array",
"items": {
"type": "string"
}
}
}
}
"""
import json
from pydantic import BaseModel
from pydantic.schema import schema
class Pets(BaseModel):
__root__: list[str]
print(Pets(__root__=['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets(__root__=['dog', 'cat']).json())
#> ["dog", "cat"]
print(Pets.parse_obj(['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets.schema())
"""
{
'title': 'Pets',
'type': 'array',
'items': {'type': 'string'},
}
"""
pets_schema = schema([Pets])
print(json.dumps(pets_schema, indent=2))
"""
{
"definitions": {
"Pets": {
"title": "Pets",
"type": "array",
"items": {
"type": "string"
}
}
}
}
"""
(这个脚本是完整的,它应该“按原样”运行)
如果您为具有自定义根类型的模型调用 parse_obj
方法,并将 dict 作为第一个参数,则使用以下逻辑:
- 如果自定义根类型是映射类型(例如,
Dict
或Mapping
),参数本身总是根据自定义根类型进行验证。 - 对于其他自定义根类型,如果字典恰好有一个值为
__root__
的键,则将根据自定义根类型验证相应的值。 - 否则,将根据自定义根类型验证字典本身。
这在以下示例中得到了证明:
from typing import List, Dict
from pydantic import BaseModel, ValidationError
class Pets(BaseModel):
__root__: List[str]
print(Pets.parse_obj(['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets.parse_obj({'__root__': ['dog', 'cat']})) # not recommended
#> __root__=['dog', 'cat']
class PetsByName(BaseModel):
__root__: Dict[str, str]
print(PetsByName.parse_obj({'Otis': 'dog', 'Milo': 'cat'}))
#> __root__={'Otis': 'dog', 'Milo': 'cat'}
try:
PetsByName.parse_obj({'__root__': {'Otis': 'dog', 'Milo': 'cat'}})
except ValidationError as e:
print(e)
"""
1 validation error for PetsByName
__root__ -> __root__
str type expected (type=type_error.str)
"""
from pydantic import BaseModel, ValidationError
class Pets(BaseModel):
__root__: list[str]
print(Pets.parse_obj(['dog', 'cat']))
#> __root__=['dog', 'cat']
print(Pets.parse_obj({'__root__': ['dog', 'cat']})) # not recommended
#> __root__=['dog', 'cat']
class PetsByName(BaseModel):
__root__: dict[str, str]
print(PetsByName.parse_obj({'Otis': 'dog', 'Milo': 'cat'}))
#> __root__={'Otis': 'dog', 'Milo': 'cat'}
try:
PetsByName.parse_obj({'__root__': {'Otis': 'dog', 'Milo': 'cat'}})
except ValidationError as e:
print(e)
"""
1 validation error for PetsByName
__root__ -> __root__
str type expected (type=type_error.str)
"""
(这个脚本是完整的,它应该“按原样”运行)
Warning
为了向后兼容,目前支持使用单键 "__root__"
在 dict 上调用 parse_obj
方法以实现向后兼容性,但不推荐并且可能在未来版本中删除。
如果您想直接访问 __root__
字段中的项目或迭代这些项目,您可以实现自定义 __iter__
和 __getitem__
函数,如以下示例所示。
from typing import List
from pydantic import BaseModel
class Pets(BaseModel):
__root__: List[str]
def __iter__(self):
return iter(self.__root__)
def __getitem__(self, item):
return self.__root__[item]
pets = Pets.parse_obj(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
from pydantic import BaseModel
class Pets(BaseModel):
__root__: list[str]
def __iter__(self):
return iter(self.__root__)
def __getitem__(self, item):
return self.__root__[item]
pets = Pets.parse_obj(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
(这个脚本是完整的,它应该“按原样”运行)
伪不变性(Faux Immutability)¶
可以通过 allow_mutation = False
将模型配置为不可变的。 设置后,尝试更改实例属性的值将引发错误。 有关 Config
的更多详细信息,请参阅 模型配置。
Warning
Python 中的不变性从来都不是严格的。 如果开发人员有决心/愚蠢,他们总是可以修改所谓的“不可变”对象。
from pydantic import BaseModel
class FooBarModel(BaseModel):
a: str
b: dict
class Config:
allow_mutation = False
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except TypeError as e:
print(e)
#> "FooBarModel" is immutable and does not support item assignment
print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}
(这个脚本是完整的,它应该“按原样”运行)
尝试更改 a
导致错误,而 a
保持不变。 然而,dict b
是可变的,而 foobar
的不变性并不能阻止 b
被改变。
抽象基类(Abstract Base Classes)¶
Pydantic 模型可以与 Python 的抽象基类 (ABC) 一起使用。
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel, abc.ABC):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
(这个脚本是完整的,它应该“按原样”运行)
字段顺序(Field Ordering)¶
字段顺序在模型中很重要,原因如下:
- 在定义的订单字段中执行验证; fields validators 可以访问前面字段的值,但不能访问后面的字段
- 字段顺序保留在模型 schema 中
- 字段顺序保留在 validation errors
- 字段顺序由
.dict()
和.json()
等保存
从 v1.0 开始,所有带有注解的字段(无论是仅注解还是带有默认值)都将位于所有没有注解的字段之前。 在各自的组中,字段保持其定义的顺序。
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
a: int
b = 2
c: int = 1
d = 0
e: float
print(Model.__fields__.keys())
#> dict_keys(['a', 'c', 'e', 'b', 'd'])
m = Model(e=2, a=1)
print(m.dict())
#> {'a': 1, 'c': 1, 'e': 2.0, 'b': 2, 'd': 0}
try:
Model(a='x', b='x', c='x', d='x', e='x')
except ValidationError as e:
error_locations = [e['loc'] for e in e.errors()]
print(error_locations)
#> [('a',), ('c',), ('e',), ('b',), ('d',)]
(这个脚本是完整的,它应该“按原样”运行)
Warning
如上面的示例所示,在同一模型中结合使用带注释和非带注释的字段可能会导致令人惊讶的字段排序。 (这是由于 Python 的限制)
因此,我们建议向所有字段添加类型注释,即使默认值会自行确定类型以保证保留字段顺序。
必须字段(Required fields)¶
要根据需要声明一个字段,您可以仅使用注解来声明它,或者您可以使用省略号(...
)作为值:
from pydantic import BaseModel, Field
class Model(BaseModel):
a: int
b: int = ...
c: int = Field(...)
(这个脚本是完整的,它应该“按原样”运行)
其中 Field
指的是 字段函数。
这里 a
、b
和 c
都是必需的。 但是,在 b
中使用省略号不适用于 mypy,从 v1.0 开始,在大多数情况下应避免使用。
必须但可选字段(Required Optional fields)¶
Warning
由于版本 v1.2 注解仅可为空(Optional[...]
、Union[None, ...]
和Any
)字段和带有省略号(...
)的可为空字段)作为默认值,不再意味着同一件事。
在某些情况下,这可能会导致 v1.2 无法完全向后兼容早期的 v1.* 版本。
如果你想指定一个字段,该字段在仍然需要时可以采用“无”值,则可以将“可选”与“...”一起使用:
from typing import Optional
from pydantic import BaseModel, Field, ValidationError
class Model(BaseModel):
a: Optional[int]
b: Optional[int] = ...
c: Optional[int] = Field(...)
print(Model(b=1, c=2))
#> a=None b=1 c=2
try:
Model(a=1, b=2)
except ValidationError as e:
print(e)
"""
1 validation error for Model
c
field required (type=value_error.missing)
"""
from pydantic import BaseModel, Field, ValidationError
class Model(BaseModel):
a: int | None
b: int | None = ...
c: int | None = Field(...)
print(Model(b=1, c=2))
#> a=None b=1 c=2
try:
Model(a=1, b=2)
except ValidationError as e:
print(e)
"""
1 validation error for Model
c
field required (type=value_error.missing)
"""
(这个脚本是完整的,它应该“按原样”运行)
在这个模型中,a
、b
和 c
可以取 None
作为值。 但是 a
是可选的,而 b
和 c
是必需的。 b
和 c
需要一个值,即使该值为 None
。
具有动态默认值的字段(Field with dynamic default value)¶
当用默认值声明一个字段时,您可能希望它是动态的(即每个模型不同)。 为此,您可能需要使用default_factory
。
测试版
default_factory
参数在 beta 中,它已在 临时基础 中添加到 v1.5 中的 pydantic。 它可能会在未来的版本中发生重大变化,并且其签名或行为在 v2 之前不会具体。 来自社区的反馈在它仍然是临时的时候将非常有用; 评论 #866 或创建一个新问题。
使用示例:
from datetime import datetime
from uuid import UUID, uuid4
from pydantic import BaseModel, Field
class Model(BaseModel):
uid: UUID = Field(default_factory=uuid4)
updated: datetime = Field(default_factory=datetime.utcnow)
m1 = Model()
m2 = Model()
print(f'{m1.uid} != {m2.uid}')
#> c8757705-8b68-4cff-9f57-8f2322e57e0e != 2a665f8e-d876-4eba-aa76-30ff2dafcebc
print(f'{m1.updated} != {m2.updated}')
#> 2023-05-08 08:38:01.396619 != 2023-05-08 08:38:01.396635
(这个脚本是完整的,它应该“按原样”运行)
其中 Field
指的是 字段函数。
Warning
default_factory
需要设置字段类型。
自动排除的属性(Automatically excluded attributes)¶
以下划线开头的类变量和用 typing.ClassVar
注释的属性将自动从模型中排除。
私有模型属性(Private model attributes)¶
如果您需要改变或操作模型实例的内部属性,您可以使用 PrivateAttr
声明它们:
from datetime import datetime
from random import randint
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str = PrivateAttr()
def __init__(self, **data):
super().__init__(**data)
# this could also be done with default_factory
self._secret_value = randint(1, 5)
m = TimeAwareModel()
print(m._processed_at)
#> 2023-05-08 16:38:01.865772
print(m._secret_value)
#> 4
(这个脚本是完整的,它应该“按原样”运行)
私有属性名称必须以下划线开头,以防止与模型字段冲突:支持 _attr
和 __attr__
。
如果 Config.underscore_attrs_are_private
为 True
,任何非 ClassVar 下划线属性都将被视为私有:
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
_class_var: ClassVar[str] = 'class var value'
_private_attr: str = 'private attr value'
class Config:
underscore_attrs_are_private = True
print(Model._class_var)
#> class var value
print(Model._private_attr)
#> <member '_private_attr' of 'Model' objects>
print(Model()._private_attr)
#> private attr value
(这个脚本是完整的,它应该“按原样”运行)
在创建类时,pydantic 构造了填充私有属性的 __slots__
。
将数据解析为指定类型(Parsing data into a specified type)¶
Pydantic 包括一个独立的实用函数 parse_obj_as
,可用于应用用于以更特殊的方式填充 pydantic 模型的解析逻辑。 此函数的行为类似于 BaseModel.parse_obj
,但适用于任意与 pydantic 兼容的类型。
当您想要将结果解析为不是 BaseModel
的直接子类的类型时,这尤其有用。
例如:
from typing import List
from pydantic import BaseModel, parse_obj_as
class Item(BaseModel):
id: int
name: str
# `item_data` could come from an API call, eg., via something like:
# item_data = requests.get('https://my-api.com/items').json()
item_data = [{'id': 1, 'name': 'My Item'}]
items = parse_obj_as(List[Item], item_data)
print(items)
#> [Item(id=1, name='My Item')]
from pydantic import BaseModel, parse_obj_as
class Item(BaseModel):
id: int
name: str
# `item_data` could come from an API call, eg., via something like:
# item_data = requests.get('https://my-api.com/items').json()
item_data = [{'id': 1, 'name': 'My Item'}]
items = parse_obj_as(list[Item], item_data)
print(items)
#> [Item(id=1, name='My Item')]
(这个脚本是完整的,它应该“按原样”运行)
此函数能够将数据解析为 pydantic 可以作为 BaseModel
字段处理的任何类型。
Pydantic 还包括两个类似的独立函数,称为 parse_file_as
和 parse_raw_as
,它们类似于 BaseModel.parse_file
和BaseModel.parse_raw
。
数据转换(Data Conversion)¶
pydantic 可能会转换输入数据以强制其符合模型字段类型,在某些情况下这可能会导致信息丢失。
例如:
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: float
c: str
print(Model(a=3.1415, b=' 2.72 ', c=123).dict())
#> {'a': 3, 'b': 2.72, 'c': '123'}
(这个脚本是完整的,它应该“按原样”运行)
这是 pydantic 深思熟虑的决定,通常这是最有用的方法。 请参阅此处,了解有关该主题的更长时间讨论。
尽管如此,部分支持严格类型检查。
模型签名(Model signature)¶
所有 pydantic 模型都将根据其字段生成签名:
import inspect
from pydantic import BaseModel, Field
class FooModel(BaseModel):
id: int
name: str = None
description: str = 'Foo'
apple: int = Field(..., alias='pear')
print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None
(这个脚本是完整的,它应该“按原样”运行)
准确的签名对于内省目的和库(如FastAPI
或 hypothesis
)很有用。
生成的签名也将遵循自定义的 __init__
函数:
import inspect
from pydantic import BaseModel
class MyModel(BaseModel):
id: int
info: str = 'Foo'
def __init__(self, id: int = 1, *, bar: str, **data) -> None:
"""My custom init!"""
super().__init__(id=id, bar=bar, **data)
print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None
(这个脚本是完整的,它应该“按原样”运行)
要包含在签名中,字段的别名或名称必须是有效的 Python 标识符。 pydantic 更喜欢别名而不是名称,但如果别名不是有效的 Python 标识符,则可以使用字段名称。
如果一个字段的别名和名称都是无效的标识符,则会添加一个 **data
参数。
此外,如果 Config.extra
为 Extra.allow
,**data
参数将始终出现在签名中。
结构模式匹配(Structural pattern matching)¶
pydantic 支持模型的结构模式匹配,如 Python 3.10 中的 PEP 636 所介绍的那样。
from pydantic import BaseModel
class Pet(BaseModel):
name: str
species: str
a = Pet(name='Bones', species='dog')
match a:
# match `species` to 'dog', declare and initialize `dog_name`
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
# default case
case _:
print('No dog matched')
(这个脚本是完整的,它应该“按原样”运行)
Note
match-case 语句看起来好像创建了一个新模型,但不要被愚弄了;
它只是获取属性并比较它或声明和初始化它的语法糖。