跳转至

Pydantic Company

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

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

模型导出

除了通过名称直接访问模型属性(例如 model.foobar)之外,还可以通过多种方式转换和导出模型:

model.dict(...)

这是将模型转换为字典的主要方式。 子模型将递归地转换为字典。

参数:

  • include: 要包含在返回字典中的字段; 见 下文
  • exclude: 从返回的字典中排除的字段; 见 下文
  • by_alias: 字段别名是否应该用作返回字典中的键; 默认false
  • exclude_unset: 是否应从返回的字典中排除在创建模型时未明确设置的字段; 默认false。 在 v1.0 之前,exclude_unset 被称为 skip_defaultsskip_defaults 的使用现已弃用
  • exclude_defaults: 是否应从返回的字典中排除等于其默认值(无论是否设置)的字段; 默认“假”
  • exclude_none: 是否应从返回的字典中排除等于None的字段; 默认false

例子:

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: float
    foo: str
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

# returns a dictionary:
print(m.dict())
"""
{
    'banana': 3.14,
    'foo': 'hello',
    'bar': {'whatever': 123},
}
"""
print(m.dict(include={'foo', 'bar'}))
#> {'foo': 'hello', 'bar': {'whatever': 123}}
print(m.dict(exclude={'foo', 'bar'}))
#> {'banana': 3.14}

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

dict(model) 和迭代(dict(model) and iteration)

pydantic 模型也可以使用 dict(model) 转换为字典,您还可以使用 for field_name, value in model: 迭代模型的字段。 使用这种方法返回原始字段值,因此子模型不会转换为字典。

例子:

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: float
    foo: str
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

print(dict(m))
"""
{
    'banana': 3.14,
    'foo': 'hello',
    'bar': BarModel(
        whatever=123,
    ),
}
"""
for name, value in m:
    print(f'{name}: {value}')
    #> banana: 3.14
    #> foo: hello
    #> bar: whatever=123

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

model.copy(...)

copy() 允许复制模型,这对于不可变模型特别有用。

参数:

  • include: 要包含在返回字典中的字段; 见 下文
  • exclude: 从返回的字典中排除的字段; 见 下文
  • update: 创建复制模型时要更改的值字典
  • deep: 是否对新模型进行深拷贝; 默认false

例子:

from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    banana: float
    foo: str
    bar: BarModel


m = FooBarModel(banana=3.14, foo='hello', bar={'whatever': 123})

print(m.copy(include={'foo', 'bar'}))
#> foo='hello' bar=BarModel(whatever=123)
print(m.copy(exclude={'foo', 'bar'}))
#> banana=3.14
print(m.copy(update={'banana': 0}))
#> banana=0 foo='hello' bar=BarModel(whatever=123)
print(id(m.bar), id(m.copy().bar))
#> 140382179354000 140382179354000
# normal copy gives the same object reference for `bar`
print(id(m.bar), id(m.copy(deep=True).bar))
#> 140382179354000 140382179355600
# deep copy gives a new object reference for `bar`

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

model.json(...)

The .json() method will serialise a model to JSON. (For models with a custom root type, only the value for the __root__ key is serialised)

参数:

  • include: 要包含在返回字典中的字段; 见 下文
  • exclude: 从返回的字典中排除的字段; 见 下文
  • by_alias: 字段别名是否应该用作返回字典中的键; 默认false
  • exclude_unset: 是否应从返回的字典中排除在创建模型时未设置且具有默认值的字段; 默认false。 在 v1.0 之前,exclude_unset 被称为 skip_defaultsskip_defaults 的使用现已弃用
  • exclude_defaults: 是否应从返回的字典中排除等于其默认值(无论是否设置)的字段; 默认false
  • exclude_none: 是否应从返回的字典中排除等于None的字段; 默认false
  • encoder: 传递给 json.dumps()default 参数的自定义编码器函数; 默认为自定义编码器,旨在处理所有常见类型。
  • **dumps_kwargs: 任何其他关键字参数都传递给 json.dumps() ,例如 indent

pydantic 可以将许多常用类型序列化为 JSON(例如datetimedateUUID),这通常会因简单的json.dumps(foobar)而失败。

from datetime import datetime
from pydantic import BaseModel


class BarModel(BaseModel):
    whatever: int


class FooBarModel(BaseModel):
    foo: datetime
    bar: BarModel


m = FooBarModel(foo=datetime(2032, 6, 1, 12, 13, 14), bar={'whatever': 123})
print(m.json())
#> {"foo": "2032-06-01T12:13:14", "bar": {"whatever": 123}}

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

json_encoders

可以使用 json_encoders 配置属性在模型上自定义序列化; 键应该是类型(或前向引用的类型名称),值应该是序列化该类型的函数(参见下面的示例):

from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat


class WithCustomEncoders(BaseModel):
    dt: datetime
    diff: timedelta

    class Config:
        json_encoders = {
            datetime: lambda v: v.timestamp(),
            timedelta: timedelta_isoformat,
        }


m = WithCustomEncoders(dt=datetime(2032, 6, 1), diff=timedelta(hours=100))
print(m.json())
#> {"dt": 1969632000.0, "diff": "P4DT4H0M0.000000S"}

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

默认情况下,timedelta被编码为总秒数的简单浮点数。 timedelta_isoformat 作为一个可选的替代方案提供,它实现了 ISO 8601 时间差异编码。

json_encoders 也在模型继承期间合并,子编码器优先于父编码器。

from datetime import datetime, timedelta
from pydantic import BaseModel
from pydantic.json import timedelta_isoformat


class BaseClassWithEncoders(BaseModel):
    dt: datetime
    diff: timedelta

    class Config:
        json_encoders = {
            datetime: lambda v: v.timestamp()
        }


class ChildClassWithEncoders(BaseClassWithEncoders):
    class Config:
        json_encoders = {
            timedelta: timedelta_isoformat
        }


m = ChildClassWithEncoders(dt=datetime(2032, 6, 1), diff=timedelta(hours=100))
print(m.json())
#> {"dt": 1969632000.0, "diff": "P4DT4H0M0.000000S"}

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

序列化自引用或其他模型(Serialising self-reference or other models)

默认情况下,模型被序列化为字典。

如果你想以不同的方式序列化它们,你可以在调用 json() 方法时添加 models_as_dict=False 并在 json_encoders 中添加模型的类。

在前向引用的情况下,您可以使用带有类名的字符串而不是类本身

from typing import List, Optional

from pydantic import BaseModel


class Address(BaseModel):
    city: str
    country: str


class User(BaseModel):
    name: str
    address: Address
    friends: Optional[List['User']] = None

    class Config:
        json_encoders = {
            Address: lambda a: f'{a.city} ({a.country})',
            'User': lambda u: f'{u.name} in {u.address.city} '
                              f'({u.address.country[:2].upper()})',
        }


User.update_forward_refs()

wolfgang = User(
    name='Wolfgang',
    address=Address(city='Berlin', country='Deutschland'),
    friends=[
        User(name='Pierre', address=Address(city='Paris', country='France')),
        User(name='John', address=Address(city='London', country='UK')),
    ],
)
print(wolfgang.json(models_as_dict=False))
#> {"name": "Wolfgang", "address": "Berlin (Deutschland)", "friends": ["Pierre
#> in Paris (FR)", "John in London (UK)"]}
from typing import Optional

from pydantic import BaseModel


class Address(BaseModel):
    city: str
    country: str


class User(BaseModel):
    name: str
    address: Address
    friends: Optional[list['User']] = None

    class Config:
        json_encoders = {
            Address: lambda a: f'{a.city} ({a.country})',
            'User': lambda u: f'{u.name} in {u.address.city} '
                              f'({u.address.country[:2].upper()})',
        }


User.update_forward_refs()

wolfgang = User(
    name='Wolfgang',
    address=Address(city='Berlin', country='Deutschland'),
    friends=[
        User(name='Pierre', address=Address(city='Paris', country='France')),
        User(name='John', address=Address(city='London', country='UK')),
    ],
)
print(wolfgang.json(models_as_dict=False))
#> {"name": "Wolfgang", "address": "Berlin (Deutschland)", "friends": ["Pierre
#> in Paris (FR)", "John in London (UK)"]}
from pydantic import BaseModel


class Address(BaseModel):
    city: str
    country: str


class User(BaseModel):
    name: str
    address: Address
    friends: list['User'] | None = None

    class Config:
        json_encoders = {
            Address: lambda a: f'{a.city} ({a.country})',
            'User': lambda u: f'{u.name} in {u.address.city} '
                              f'({u.address.country[:2].upper()})',
        }


User.update_forward_refs()

wolfgang = User(
    name='Wolfgang',
    address=Address(city='Berlin', country='Deutschland'),
    friends=[
        User(name='Pierre', address=Address(city='Paris', country='France')),
        User(name='John', address=Address(city='London', country='UK')),
    ],
)
print(wolfgang.json(models_as_dict=False))
#> {"name": "Wolfgang", "address": "Berlin (Deutschland)", "friends": ["Pierre
#> in Paris (FR)", "John in London (UK)"]}

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

序列化子类(Serialising subclasses)

Note

版本 v1.5 中的新功能。

v1.5 之前,普通类型的子类不会自动序列化为 JSON。

公共类型的子类像它们的超类一样自动编码:

from datetime import date, timedelta
from pydantic import BaseModel
from pydantic.validators import int_validator


class DayThisYear(date):
    """
    Contrived example of a special type of date that
    takes an int and interprets it as a day in the current year
    """

    @classmethod
    def __get_validators__(cls):
        yield int_validator
        yield cls.validate

    @classmethod
    def validate(cls, v: int):
        return date.today().replace(month=1, day=1) + timedelta(days=v)


class FooModel(BaseModel):
    date: DayThisYear


m = FooModel(date=300)
print(m.json())
#> {"date": "2023-10-28"}

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

自定义 JSON(反)序列化(Custom JSON (de)serialisation)

为了提高编码和解码 JSON 的性能,可以通过 Configjson_loadsjson_dumps 属性使用替代 JSON 实现(例如 ujson) `。

from datetime import datetime
import ujson
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None

    class Config:
        json_loads = ujson.loads


user = User.parse_raw('{"id": 123,"signup_ts":1234567890,"name":"John Doe"}')
print(user)
#> id=123 signup_ts=datetime.datetime(2009, 2, 13, 23, 31, 30,
#> tzinfo=datetime.timezone.utc) name='John Doe'

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

ujson 通常不能用于转储 JSON,因为它不支持日期时间等对象的编码,并且不接受 default 回退函数参数。 为此,您可以使用另一个库,例如 orjson

from datetime import datetime
import orjson
from pydantic import BaseModel


def orjson_dumps(v, *, default):
    # orjson.dumps returns bytes, to match standard json.dumps we need to decode
    return orjson.dumps(v, default=default).decode()


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None

    class Config:
        json_loads = orjson.loads
        json_dumps = orjson_dumps


user = User.parse_raw('{"id":123,"signup_ts":1234567890,"name":"John Doe"}')
print(user.json())
#> {"id":123,"signup_ts":"2009-02-13T23:31:30+00:00","name":"John Doe"}

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

请注意,orjson 本身负责处理 datetime 编码,使其比 json.dumps 更快,但这意味着您不能总是使用 Config.json_encoders 自定义编码。

pickle.dumps(model)

使用与 copy() 相同的方案,pydantic 模型支持高效的 pickling 和 unpickling。

import pickle
from pydantic import BaseModel


class FooBarModel(BaseModel):
    a: str
    b: int


m = FooBarModel(a='hello', b=123)
print(m)
#> a='hello' b=123
data = pickle.dumps(m)
print(data)
"""
b'\x80\x04\x95\x8e\x00\x00\x00\x00\x00\x00\x00\x8c\x17exporting_models_pickle
\x94\x8c\x0bFooBarModel\x94\x93\x94)\x81\x94}\x94(\x8c\x08__dict__\x94}\x94(\
x8c\x01a\x94\x8c\x05hello\x94\x8c\x01b\x94K{u\x8c\x0e__fields_set__\x94\x8f\x
94(h\th\x07\x90\x8c\x1c__private_attribute_values__\x94}\x94ub.'
"""
m2 = pickle.loads(data)
print(m2)
#> a='hello' b=123

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

高级包含和排除(Advanced include and exclude)

dictjsoncopy 方法支持 includeexclude 参数,它们可以是集合或字典。 这允许嵌套选择要导出的字段:

from pydantic import BaseModel, SecretStr


class User(BaseModel):
    id: int
    username: str
    password: SecretStr


class Transaction(BaseModel):
    id: str
    user: User
    value: int


t = Transaction(
    id='1234567890',
    user=User(
        id=42,
        username='JohnDoe',
        password='hashedpassword'
    ),
    value=9876543210,
)

# using a set:
print(t.dict(exclude={'user', 'value'}))
#> {'id': '1234567890'}

# using a dict:
print(t.dict(exclude={'user': {'username', 'password'}, 'value': True}))
#> {'id': '1234567890', 'user': {'id': 42}}

print(t.dict(include={'id': True, 'user': {'id'}}))
#> {'id': '1234567890', 'user': {'id': 42}}

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

True 表示我们想要排除或包含整个键,就好像我们将它包含在一个集合中一样。 当然,可以在任何深度级别进行相同的操作。

在从子模型或字典的列表或元组中包含或排除字段时必须特别小心。 在这种情况下,dict 和相关方法需要整数键来按元素包含或排除。 要从列表或元组的每个成员中排除一个字段,可以使用字典键__all__,如下所示:

import datetime
from typing import List

from pydantic import BaseModel, SecretStr


class Country(BaseModel):
    name: str
    phone_code: int


class Address(BaseModel):
    post_code: int
    country: Country


class CardDetails(BaseModel):
    number: SecretStr
    expires: datetime.date


class Hobby(BaseModel):
    name: str
    info: str


class User(BaseModel):
    first_name: str
    second_name: str
    address: Address
    card_details: CardDetails
    hobbies: List[Hobby]


user = User(
    first_name='John',
    second_name='Doe',
    address=Address(
        post_code=123456,
        country=Country(
            name='USA',
            phone_code=1
        )
    ),
    card_details=CardDetails(
        number=4212934504460000,
        expires=datetime.date(2020, 5, 1)
    ),
    hobbies=[
        Hobby(name='Programming', info='Writing code and stuff'),
        Hobby(name='Gaming', info='Hell Yeah!!!'),
    ],
)

exclude_keys = {
    'second_name': True,
    'address': {'post_code': True, 'country': {'phone_code'}},
    'card_details': True,
    # You can exclude fields from specific members of a tuple/list by index:
    'hobbies': {-1: {'info'}},
}

include_keys = {
    'first_name': True,
    'address': {'country': {'name'}},
    'hobbies': {0: True, -1: {'name'}},
}

# would be the same as user.dict(exclude=exclude_keys) in this case:
print(user.dict(include=include_keys))
"""
{
    'first_name': 'John',
    'address': {'country': {'name': 'USA'}},
    'hobbies': [
        {
            'name': 'Programming',
            'info': 'Writing code and stuff',
        },
        {'name': 'Gaming'},
    ],
}
"""

# To exclude a field from all members of a nested list or tuple, use "__all__":
print(user.dict(exclude={'hobbies': {'__all__': {'info'}}}))
"""
{
    'first_name': 'John',
    'second_name': 'Doe',
    'address': {
        'post_code': 123456,
        'country': {'name': 'USA', 'phone_code': 1},
    },
    'card_details': {
        'number': SecretStr('**********'),
        'expires': datetime.date(2020, 5, 1),
    },
    'hobbies': [{'name': 'Programming'}, {'name': 'Gaming'}],
}
"""
import datetime

from pydantic import BaseModel, SecretStr


class Country(BaseModel):
    name: str
    phone_code: int


class Address(BaseModel):
    post_code: int
    country: Country


class CardDetails(BaseModel):
    number: SecretStr
    expires: datetime.date


class Hobby(BaseModel):
    name: str
    info: str


class User(BaseModel):
    first_name: str
    second_name: str
    address: Address
    card_details: CardDetails
    hobbies: list[Hobby]


user = User(
    first_name='John',
    second_name='Doe',
    address=Address(
        post_code=123456,
        country=Country(
            name='USA',
            phone_code=1
        )
    ),
    card_details=CardDetails(
        number=4212934504460000,
        expires=datetime.date(2020, 5, 1)
    ),
    hobbies=[
        Hobby(name='Programming', info='Writing code and stuff'),
        Hobby(name='Gaming', info='Hell Yeah!!!'),
    ],
)

exclude_keys = {
    'second_name': True,
    'address': {'post_code': True, 'country': {'phone_code'}},
    'card_details': True,
    # You can exclude fields from specific members of a tuple/list by index:
    'hobbies': {-1: {'info'}},
}

include_keys = {
    'first_name': True,
    'address': {'country': {'name'}},
    'hobbies': {0: True, -1: {'name'}},
}

# would be the same as user.dict(exclude=exclude_keys) in this case:
print(user.dict(include=include_keys))
"""
{
    'first_name': 'John',
    'address': {'country': {'name': 'USA'}},
    'hobbies': [
        {
            'name': 'Programming',
            'info': 'Writing code and stuff',
        },
        {'name': 'Gaming'},
    ],
}
"""

# To exclude a field from all members of a nested list or tuple, use "__all__":
print(user.dict(exclude={'hobbies': {'__all__': {'info'}}}))
"""
{
    'first_name': 'John',
    'second_name': 'Doe',
    'address': {
        'post_code': 123456,
        'country': {'name': 'USA', 'phone_code': 1},
    },
    'card_details': {
        'number': SecretStr('**********'),
        'expires': datetime.date(2020, 5, 1),
    },
    'hobbies': [{'name': 'Programming'}, {'name': 'Gaming'}],
}
"""

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

jsoncopy 方法也是如此。

模型和字段级别包含和排除(Model and field level include and exclude)

除了传递给 dictjsoncopy 方法的显式参数 excludeinclude 之外,我们还可以将 include/exclude 参数直接传递给 Field 构造函数或 模型Config类中的等效Field实例:

from pydantic import BaseModel, Field, SecretStr


class User(BaseModel):
    id: int
    username: str
    password: SecretStr = Field(..., exclude=True)


class Transaction(BaseModel):
    id: str
    user: User = Field(..., exclude={'username'})
    value: int

    class Config:
        fields = {'value': {'exclude': True}}


t = Transaction(
    id='1234567890',
    user=User(
        id=42,
        username='JohnDoe',
        password='hashedpassword'
    ),
    value=9876543210,
)

print(t.dict())
#> {'id': '1234567890', 'user': {'id': 42}}

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

在使用多种策略的情况下,exclude/include字段按照以下规则进行合并:

  • 首先,模型配置级别设置(通过Field实例)按字段与字段构造器设置(即Field(..., exclude=True))合并,字段构造器优先。
  • 结果设置按类与 dictjsoncopy 调用的显式设置合并,显式设置优先。

请注意,在合并设置时,exclude 通过计算键的union合并,而include 通过计算键的交集(intersection)合并。

生成的合并排除设置:

from pydantic import BaseModel, Field, SecretStr


class User(BaseModel):
    id: int
    username: str  # overridden by explicit exclude
    password: SecretStr = Field(exclude=True)


class Transaction(BaseModel):
    id: str
    user: User
    value: int


t = Transaction(
    id='1234567890',
    user=User(
        id=42,
        username='JohnDoe',
        password='hashedpassword'
    ),
    value=9876543210,
)

print(t.dict(exclude={'value': True, 'user': {'username'}}))
#> {'id': '1234567890', 'user': {'id': 42}}

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

与使用合并包含设置相同,如下所示:

from pydantic import BaseModel, Field, SecretStr


class User(BaseModel):
    id: int = Field(..., include=True)
    username: str = Field(..., include=True)  # overridden by explicit include
    password: SecretStr


class Transaction(BaseModel):
    id: str
    user: User
    value: int


t = Transaction(
    id='1234567890',
    user=User(
        id=42,
        username='JohnDoe',
        password='hashedpassword'
    ),
    value=9876543210,
)

print(t.dict(include={'id': True, 'user': {'id'}}))
#> {'id': '1234567890', 'user': {'id': 42}}

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