跳转至

Pydantic Company

我们根据我认为导致 Pydantic 成功的原则创办了一家公司。

公司公告中了解更多信息.

模型

在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')

这儿的 userUser 的实例,对象的初始化将执行所有的解析和验证。如果没有引发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 对象的模型。

去做这个:

  1. Config 的属性 orm_mode 必须设置为 True.
  2. 必须使用特殊构造函数 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

上面的示例之所以有效,是因为别名优先于字段填充的字段名称。 访问 SQLModelmetadata 属性会导致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=Trueroot_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 本身,而是引发 ValueErrorTypeErrorAssertionError(或 ValueErrorTypeError 的子类),它们将被捕获并用于填充 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)

在您的自定义数据类型或验证器中,您应该使用 ValueErrorTypeErrorAssertionError 来引发错误。

有关使用 @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: 这需要 strbytes 并将其解析为 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.GenericModeltyping.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 还像处理 ListDict 等内置泛型类型一样处理 GenericModel,以使其保持未参数化或使用有界 TypeVar 实例:

  • 如果您在实例化通用模型之前没有指定参数,它们将被视为Any
  • 您可以使用一个或多个bounded(有界)参数对模型进行参数化以添加子类检查

此外,与 ListDict 一样,使用 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

(这个脚本是完整的,它应该“按原样”运行)

这里的 StaticFoobarModelDynamicFoobarModel 是相同的。

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)
    """

(这个脚本是完整的,它应该“按原样”运行)

NamedTupleTypedDict创建模型(Model creation from NamedTuple or TypedDict)

有时,您已经在应用程序中使用了继承自 NamedTupleTypedDict 的类,并且您不想复制所有信息以拥有 BaseModel。 为此pydantic 提供了create_model_from_namedtuplecreate_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 作为第一个参数,则使用以下逻辑:

  • 如果自定义根类型是映射类型(例如,DictMapping),参数本身总是根据自定义根类型进行验证。
  • 对于其他自定义根类型,如果字典恰好有一个值为 __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)

字段顺序在模型中很重要,原因如下:

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 指的是 字段函数

这里 abc 都是必需的。 但是,在 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)
    """

(这个脚本是完整的,它应该“按原样”运行)

在这个模型中,abc 可以取 None 作为值。 但是 a 是可选的,而 bc 是必需的。 bc 需要一个值,即使该值为 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_privateTrue,任何非 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_asparse_raw_as,它们类似于 BaseModel.parse_fileBaseModel.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

(这个脚本是完整的,它应该“按原样”运行)

准确的签名对于内省目的和库(如FastAPIhypothesis )很有用。

生成的签名也将遵循自定义的 __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.extraExtra.allow**data 参数将始终出现在签名中。

Note

模型签名中的类型与模型注释中声明的类型相同,不一定是实际可以提供给该字段的所有类型。

一旦 #1055 得到解决,这可能会在某一天得到解决。

结构模式匹配(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 语句看起来好像创建了一个新模型,但不要被愚弄了;

它只是获取属性并比较它或声明和初始化它的语法糖。