目录
  • 基本原理
  • 安装
  • 用法
  • dataclasses
    • 嵌套的dataclasses
  • 选择
  • 验证器
    • pre和whole验证
    • always验证
    • dataclass验证器
    • 字段检查
  • 递归模型
  • 模式创建
  • 错误处理
  • datetime类型
  • Exotic类型
  • Secret类型
  • JSON类型
  • 自定义数据类型
  • 帮助函数
  • 模型的 Config 类
  • 设置
  • 动态模型创建
  • 与mypy一起使用
    • 严格的可选项
    • 必须字段和mypy
  • 伪不可变性
  • 复制
  • 序列化
    • JSON序列化
    • Pickle序列化
  • 抽象基类
  • 延迟注解

Pydantic 是一个使用Python类型提示来进行数据验证和设置管理的库。Pydantic定义数据应该如何使用纯Python规范用并进行验证 。PEP 484 从Python3.5开始引入了类型提示的功能,PEP 526 使用Python3.6中的变量注释语法对其进行了拓展。Pydantic使用这些注释来验证不受信任的数据是否采用了您想要的形式。
示例:
from datetime import datetime
from typing import List
from pydantic import BaseModel

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

external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123

这里发生了什么:

  • id 是 int 类型;注释声明告诉pydantic该字段是必须的 。如果可能 ,字符串、字节或浮点数将强制转换为int,否则将引发异常。
  • name 从默认值推断为其为 str 类型,该字段不是必须的 ,因为它有默认值。
  • signup_ts 是 datetime 类型,该字段不是必须的,默认值为 None 。pydantic会将表示unix时间戳(例如1496498400)的 int 类型或表示时间和日期的字符串处理成 datetime 类型。
  • friends 使用Python的 typing 系统 ,该字段是必须的,并且必须是元素为整数的列表,默认值为一个空列表。

如果验证失败 ,pydantic会抛出一个错误 ,列出错误的原因:

from pydantic import ValidationError
try:
    User(signup_ts='broken', friends=[1, 2, 'not number'])
except ValidationError as e:
    print(e.json())

"""
[
  {
    "loc": [
      "id"
    ],
    "msg": "field required",
    "type": "value_error.missing"
  },
  {
    "loc": [
      "signup_ts"
    ],
    "msg": "invalid datetime format",
    "type": "type_error.datetime"
  },
  {
    "loc": [
      "friends",
      2
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  }
]
"""

基本原理

pydantic使用了一些很酷的新语言特性,但我为什么要使用它呢?

  • 不需要太高的学习成本

不需要学习新的模式定义微语言 。如果您了解Python(也许略读了 类型提示文档),您就知道如何使用pydantic 。

  • 与你的IDE/linter/brain配合得很好

因为pydantic数据结构只是您定义的类的实例;自动完成 、linting、mypy和您的直觉都应该能够正确地处理经过验证的数据。

  • 多用途

pydantic的 BaseSettions 类允许在 验证此请求数据 上下文和 加载我的系统设置 上下文中使用它。主要区别在于 ,系统设置可以由环境变量更改默认值,而且通常需要更复杂的对象,如DSNs和Python对象 。

  • 快速

pydantic比其他所有测试库都要快。

  • 可以验证复杂结构

使用递归pydantic模型、typing 模块的 List 和 Dict 等 ,并且验证器可以清晰 、轻松地定义复杂的数据模式,然后进行检查。

  • 可拓展

pydantic允许定义自定义数据类型,或者您可以使用使用validator装饰器装饰的模型上的方法来扩展验证器 。

安装

pip install pydantic

Pydantic除了Python3.6或Python3.7(和Python3.6中的 dataclasses 包)之外 ,不需要其他依赖项。

如果想让pydantic更加快速的解析JSON,你可以添加 ujson 作为可选的依赖项。类似的,pydantic 的email验证依赖于 email-validator :

pip install pydantic[ujson]
# or
pip install pydantic[email]
# or just
pip install pydantic[ujson,email]

当然 ,你也可以使用 pip install … 手动安装这些依赖项 。

用法

pydantic使用 typing 类型定义更复杂的对象:

from typing import Dict, List, Optional, Sequence, Set, Tuple, Union
from pydantic import BaseModel


class Model(BaseModel):
    simple_list: list = None
    list_of_ints: List[int] = None

    simple_tuple: tuple = None
    tuple_of_different_types: Tuple[int, float, str, bool] = None

    simple_dict: dict = None
    dict_str_float: Dict[str, float] = None

    simple_set: set = None
    set_bytes: Set[bytes] = None

    str_or_bytes: Union[str, bytes] = None
    none_or_str: Optional[str] = None

    sequence_of_ints: Sequence[int] = None
    compound: Dict[Union[str, bytes], List[Set[int]]] = None


print(Model(simple_list=['1', '2', '3']).simple_list)  # > ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints)  # > [1, 2, 3]

print(Model(simple_tuple=(1, 2, 3, 4)).simple_tuple)  # > (1, 2, 3, 4)
print(Model(tuple_of_different_types=[1, 2, 3, 4]).tuple_of_different_types)  # > (1, 2.0, '3', True)

print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict)  # > {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float)  # > {'a': 1.0, 'b': 2.0}

print(Model(simple_set={1, 2, 3, 4}).simple_set)
print(Model(set_bytes={b'1', b'2', b'3', b'4'}).set_bytes)

print(Model(str_or_bytes='hello world').str_or_bytes)
print(Model(str_or_bytes=b'hello world').str_or_bytes)
print(Model(none_or_str='hello world').none_or_str)
print(Model(none_or_str=None).none_or_str)

print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)  # > [1, 2, 3, 4]
print(Model(compound={'name': [{1, 2, 3}], b'entitlement': [{10, 5}]}).compound)

dataclasses

# 注意:v0.14 版本的新功能。

如果不希望使用pydantic的 BaseModel,则可以在标准 dataclasses 上获得相同的数据验证(在Python 3.7中引入)。

dataclasses 在Python3.6中使用 dataclasses backport package 来工作 。

from datetime import datetime
from pydantic.dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str = 'John Doe'
    signup_ts: datetime = None


user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
# > User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))

您可以使用所有标准的pydantic字段类型,得到的数据类将与使用标准库的 dataclass 装饰器创建的数据类相同。

pydantic.dataclasses.dataclass 的参数与标准装饰器相同 ,除了一个与 Config 具有相同含义的额外关键字参数 config。

注意:作为pydantic的 dataclasses 能够很好地处理 mypy的副作用, 配置参数将在IDE和mypy中显示为不可用	。使用 @dataclass(..., config=Config) # type: ignore 作为解决方案。查看 python/mypy#6239 来了解为什么要这样。

嵌套的dataclasses

从 v0.17 版本开始,dataclasses 和正常的模型都支持嵌套的 dataclasses。

from pydantic import UrlStr
from pydantic.dataclasses import dataclass

@dataclass
class NavbarButton:
    href: UrlStr

@dataclass
class Navbar:
    button: NavbarButton

navbar = Navbar(button=('https://example.com',))
print(navbar)
# > Navbar(button=NavbarButton(href='https://example.com'))

dataclasses 属性可以由元组、字典或该 dataclass 的示例来填充 。

选择

Pydantic使用Python的标准 enum 类定义选择:

from enum import Enum, IntEnum

from pydantic import BaseModel


class FruitEnum(str, Enum):
    pear = 'pear'
    banana = 'banana'


class ToolEnum(IntEnum):
    spanner = 1
    wrench = 2


class CookingModel(BaseModel):
    fruit: FruitEnum = FruitEnum.pear
    tool: ToolEnum = ToolEnum.spanner


print(CookingModel())
# > CookingModel fruit=<FruitEnum.pear: 'pear'> tool=<ToolEnum.spanner: 1>
print(CookingModel(tool=2, fruit='banana'))
# > CookingModel fruit=<FruitEnum.banana: 'banana'> tool=<ToolEnum.wrench: 2>
print(CookingModel(fruit='other'))
# will raise a validation error

验证器

自定义验证和对象之间的复杂关系可以使用 validator 装饰器来获得。

validator 装饰器通过装饰类中的方法来验证字段,被装饰的方法必须是 类方法。

validator 装饰器有几个可选的参数:

  • fields:要调用装饰器进行验证的字段 。一个验证器可以应用于多个字段 ,可以通过显式指定字段名称的方式逐一指定字段,也可以通过 * 以解包的方式指定所有的字段,这意味着将为所有字段调用验证器。
  • pre:是否应该在标准验证器之前调用此验证器(否则在之后)。如果为 True ,则在调用标准验证器之前调用,否则在之后调用 。
  • whole:对于复杂的对象。例如 set 或者 list,是验证对象中的每个元素或者验证整个对象。如果为 True ,则验证对象本身,如果为 False,则验证对象中的每个元素 。
  • always:是否在值缺失的情况下仍然调这个方法和其他验证器。
  • check_fields:是否检查模型上是否存在字段。
from pydantic import BaseModel, ValidationError, validator


class UserModel(BaseModel):
    name: str
    password1: str
    password2: str

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v


UserModel(name='samuel colvin', password1='zxcvbn', password2='zxcvbn')
# <UserModel name='Samuel Colvin' password1='zxcvbn' password2='zxcvbn'>
try:
    UserModel(name='samuel', password1='zxcvbn', password2='zxcvbn2')
except ValidationError as e:
    print(e)

# 2 validation errors
# name
#   must contain a space (type=value_error)
# password2
#   passwords do not match (type=value_error)

需要注意的几件事情:

  • validator 装饰的是类方法 。它接收的第一个值是 UserModel 而不是 UserModel 的实例 。
  • 它们的签名可以是 (cls, value) 或 (cls, value, values, config, field)。从 v0.20开始 ,任何 (values, config, field) 的子集都是允许的。例如 (cls, value, field),但是,由于检查 validator 的方式 ,可变的关键字参数(**kwargs)必须叫做 kwargs 。
  • validator 应该返回新的值或者引发 ValueError 或 TypeError 异常。
  • 当验证器依赖于其他值时 ,应该知道:
    • 验证是按照字段的定义顺序来完成的,例如。这里 password2 可以访问 password1 (和 name),但是password1 不能访问 password2 。应该注意以下关于字段顺序和必填字段的警告。
    • 如果在其另一个字段上验证失败(或者那个字段缺失) ,则其不会被包含在 values 中,因此,上面的例子中包含 if 'password1' in values and … 语句。
注意:从v0.18开始	,磨人不会对字典的键调用验证器	。如果希望验证键,请使用 whole 。

pre和whole验证

import json
from typing import List
from pydantic import BaseModel, ValidationError, validator


class DemoModel(BaseModel):
    numbers: List[int] = []
    people: List[str] = []

    @validator('people', 'numbers', pre=True, whole=True)
    def json_decode(cls, v):
        if isinstance(v, str):
            try:
                return json.loads(v)
            except ValueError:
                pass
        return v

    @validator('numbers')
    def check_numbers_low(cls, v):
        if v > 4:
            raise ValueError(f'number too large {v} > 4')
        return v

    @validator('numbers', whole=True)
    def check_sum_numbers_low(cls, v):
        if sum(v) > 8:
            raise ValueError(f'sum of numbers greater than 8')
        return v


DemoModel(numbers='[1, 2, 1, 3]')
# <DemoModel numbers=[1, 2, 1, 3] people=[]>
try:
    DemoModel(numbers='[1, 2, 5]')
except ValidationError as e:
    print(e)

# 1 validation error
# numbers -> 2
#   number too large 5 > 4 (type=value_error)
try:
    DemoModel(numbers=[3, 3, 3])
except ValidationError as e:
    print(e)

# 1 validation error
# numbers
#   sum of numbers greater than 8 (type=value_error)

always验证

出于性能原因,默认情况下 ,对于不提供值的字段不调用验证器。然而,在某些情况下,总是调用验证器是有用的或必需的 ,例如设置动态默认值 。

from datetime import datetime
from pydantic import BaseModel, validator


class DemoModel(BaseModel):
    ts: datetime = None

    @validator('ts', pre=True, always=True)
    def set_ts_now(cls, v):
        return v or datetime.now()


DemoModel()
# <DemoModel ts=datetime.datetime(2019, 4, 26, 8, 26, 13, 74477)>
DemoModel(ts='2017-11-08T14:00')
# <DemoModel ts=datetime.datetime(2017, 11, 8, 14, 0)>

dataclass验证器

验证器在 dataclasses 上也可以工作:

from datetime import datetime
from pydantic import validator
from pydantic.dataclasses import dataclass


@dataclass
class DemoDataclass:
    ts: datetime = None

    @validator('ts', pre=True, always=True)
    def set_ts_now(cls, v):
        return v or datetime.now()


DemoDataclass()
DemoDataclass(ts=None)
DemoDataclass(ts='2017-11-08T14:00')
DemoDataclass(ts=datetime.datetime(2017, 11, 8, 14, 0))

字段检查

在类创建时,将检查验证器,以确认它们指定的字段实际上存在于模型中。

但是 ,有时并不需要这样做:当定义一个验证器来验证继承模型上的字段时。在这种情况下 ,您应该在验证器上设置 check_fields=False 。

递归模型

更复杂的层次数据结构可以使用模型作为注解中的类型来定义 。

... 只表示与上面的注解声明相同的 Required。

from typing import List
from pydantic import BaseModel


class Foo(BaseModel):
    count: int = ...
    size: float = None


class Bar(BaseModel):
    apple = 'x'
    banana = 'y'


class Spam(BaseModel):
    foo: Foo = ...
    bars: List[Bar] = ...


s = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])

# <Spam foo=<Foo count=4 size=None> bars=[<Bar apple='x1' banana='y'>, <Bar apple='x2' banana='y'>]>
s.dict()
# {'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}

模式创建

Pydantic会自动从模型创建JSON Schema。

from enum import Enum
from pydantic import BaseModel, Schema


class FooBar(BaseModel):
    count: int
    size: float = None


class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'


class MainModel(BaseModel):
    """
    This is the description of the main model
    """
    foo_bar: FooBar = Schema(...)
    gender: Gender = Schema(
        None,
        alias='Gender',
    )
    snap: int = Schema(
        42,
        title='The Snap',
        description='this is the value of snap',
        gt=30,
        lt=50,
    )

    class Config:
        title = 'Main'


print(MainModel.schema())
# {'title': 'Main', 'description': 'This is the description of the main model', 'type': 'object', 'properties': {'foo_bar': {'$ref': '#/definitions/FooBar'}, 'Gender': {'title': 'Gender', 'enum': ['male', 'female', 'other', 'not_given'], 'type': 'string'}, 'snap': {'title': 'The Snap', 'description': 'this is the value of snap', 'default': 42, 'exclusiveMinimum': 30, 'exclusiveMaximum': 50, 'type': 'integer'}}, 'required': ['foo_bar'], 'definitions': {'FooBar': {'title': 'FooBar', 'type': 'object', 'properties': {'count': {'title': 'Count', 'type': 'integer'}, 'size': {'title': 'Size', 'type': 'number'}}, 'required': ['count']}}}
print(MainModel.schema_json(indent=4))

输出:

{
    "title": "Main",
    "description": "This is the description of the main model",
    "type": "object",
    "properties": {
        "foo_bar": {
            "$ref": "#/definitions/FooBar"
        },
        "Gender": {
            "title": "Gender",
            "enum": [
                "male",
                "female",
                "other",
                "not_given"
            ],
            "type": "string"
        },
        "snap": {
            "title": "The Snap",
            "description": "this is the value of snap",
            "default": 42,
            "exclusiveMinimum": 30,
            "exclusiveMaximum": 50,
            "type": "integer"
        }
    },
    "required": [
        "foo_bar"
    ],
    "definitions": {
        "FooBar": {
            "title": "FooBar",
            "type": "object",
            "properties": {
                "count": {
                    "title": "Count",
                    "type": "integer"
                },
                "size": {
                    "title": "Size",
                    "type": "number"
                }
            },
            "required": [
                "count"
            ]
        }
    }
}

生成的模式符合以下规范: JSON Schema Core,JSON Schema Validation and OpenAPI 。

BaseModel.schema 会返回一个表示JSON Schema的字典对象。

BaseModel.schema_json 会返回一个表示表示JSON Schema的字符串。

使用的子模型被添加到JSON Schema中,并根据规范进行引用 。所有子模型(及其子模型)模式都会被直接放在JSON Schema的顶级键值对中 ,便于重用和引用。所有通过 Schema 类进行修改的 子模型 ,比如自定义标题、描述和默认值,都会被递归地包含 ,而不是引用。模型的描述从类的文档字符串或 Schema 类的参数描述中获得 。

Schema类以可选的方式提供关于字段和验证 、参数的额外信息:

  • default:位置参数。因为 Schema 类会替换字段的默认值,它的第一个参数用来设置默认值。使用 ... 表示这个字段是必须的 。
  • alias:字段的公共名称。
  • title:如果未指定该参数,则使用 field_name.title()。
  • description:如果未指定该参数 ,并且注解是一个子模型,将使用子模型的文档字符串 。
  • gt:对于数值型的值(int, float, Decimal),在JSON Schema中添加一个 大于 验证和一个 exclusiveMinimum 注解 。
  • ge:对于数值型的值(int, float, Decimal) ,在JSON Schema中添加一个 大于等于 验证和一个 minimum 注解。
  • lt:对于数值型的值(int, float, Decimal),在JSON Schema中添加一个 小于 验证和一个 exclusiveMaximum 注解。
  • le:对于数值型的值(int, float, Decimal),在JSON Schema中添加一个 小于等于 验证和一个 maximum 注解 。
    multiple_of:对于数值型的值(int, float, Decimal) ,在JSON Schema中添加一个 倍数 验证和一个 multipleOf 注解。
  • min_length:对于字符串类型的值 ,在JSON Schema中添加一个相应的验证和 minLength 注释。
  • max_length:对于字符串类型的值,在JSON Schema中添加一个相应的验证和 maxLength 注释 。
  • regex:对于字符串类型的值,在JSON Schema中添加一个由给定的字符串生成的正则表达式验证和一个 pattern 注解。
  • **:任何其他关键字参数(例如 example )将会被逐个添加到字段的模式。

Config 类的 fields 特性代替 Schema 类用以设置以上参数中除过 default 参数的其他参数的值 。

默认情况下 ,模式是使用别名作为键生成的,也可以使用模型属性名而不是使用别名生成:

MainModel.schema/schema_json(by_alias=False)

当有一个等价物可用时,类型、自定义字段类型和约束(如 max_length)映射到相应的 JSON Schema Core 规范格式 ,接下来, JSON Schema Validation, OpenAPI Data Types(基于JSON模式)等将以标准的JSON Schema验证关键字 format 为更复杂的 string 类型定义Pydantic子类型扩展。

要查看从Python/Pydantic 到 JSON Schema的字段模式映射 ,请参考 字段模式映射。

您还可以生成一个顶级JSON模式,该JSON模式的 definitions 中只包含一个模型列表及其所有相关子模块:

import json
from pydantic import BaseModel
from pydantic.schema import schema


class Foo(BaseModel):
    a: str = None


class Model(BaseModel):
    b: Foo


class Bar(BaseModel):
    c: int


top_level_schema = schema([Model, Bar], title='My Schema')
print(json.dumps(top_level_schema, indent=4))
# {
#     "title": "My Schema",
#     "definitions": {
#         "Foo": {
#             "title": "Foo",
#             "type": "object",
#             "properties": {
#                 "a": {
#                     "title": "A",
#                     "type": "string"
#                 }
#             }
#         },
#         "Model": {
#             "title": "Model",
#             "type": "object",
#             "properties": {
#                 "b": {
#                     "$ref": "#/definitions/Foo"
#                 }
#             },
#             "required": [
#                 "b"
#             ]
#         },
#         "Bar": {
#             "title": "Bar",
#             "type": "object",
#             "properties": {
#                 "c": {
#                     "title": "C",
#                     "type": "integer"
#                 }
#             },
#             "required": [
#                 "c"
#             ]
#         }
#     }
# }

您可以自定义生成的 $ref ,$ref 的值的仍然在键定义中指定 ,您仍然可以从键定义中获取键值,但是引用将指向你定义的前缀,而不是默认的前缀 。

扩展或修改JSON模式的默认定义位置非常有用 ,例如使用OpenAPI:

import json
from pydantic import BaseModel
from pydantic.schema import schema


class Foo(BaseModel):
    a: int


class Model(BaseModel):
    a: Foo


top_level_schema = schema([Model], ref_prefix='#/components/schemas/')  # Default location for OpenAPI
print(json.dumps(top_level_schema, indent=4))
# {
#     "definitions": {
#         "Foo": {
#             "title": "Foo",
#             "type": "object",
#             "properties": {
#                 "a": {
#                     "title": "A",
#                     "type": "integer"
#                 }
#             },
#             "required": [
#                 "a"
#             ]
#         },
#         "Model": {
#             "title": "Model",
#             "type": "object",
#             "properties": {
#                 "a": {
#                     "$ref": "#/components/schemas/Foo"
#                 }
#             },
#             "required": [
#                 "a"
#             ]
#         }
#     }
# }

错误处理

当Pydantic在它正在验证的数据中发现错误时 ,就会引发 ValidationError 异常。

注意:验证代码不应该引发 ValidationError 异常,而应该引发将会被捕获并用于填充 ValidationError异常的 ValueError 或 TypeError (或其子类)异常。	。

无论发现多少错误,都只会引发一个异常 ,ValidationError 将包含关于所有错误及其发生方式的信息 。

你可以通过下面几种方式访问这些错误:

  • e.errors():将输入数据中发现的错误作为一个列表返回。
  • e.json():返回表示 e.errors 的JSON。
  • str(e):将 e.errors 以人类可读的字符串返回 。

每一个 error 对象包含以下属性:

  • 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
# 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"
#   }
# ]

如果你自定义了数据类型或者验证器,你应该使用 TypeErrorValueError 引发错误:

from pydantic import BaseModel, ValidationError, validator


class Model(BaseModel):
    foo: str

    @validator('foo')
    def name_must_contain_space(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 name_must_contain_space(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"
#     }
#   }
# ]

datetime类型

Pydantic支持以下的datetime类型:

  • datetime 字段可以是:

    • datetime:已存在的datetime对象
    • int或float:假定为自 1970年1月1日的Unix时间 ,例如,秒(如果小于等于2e10)或毫秒(如果大于2e10)
    • str:支持如下格式:
      • YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z[±]HH[:]MM]]]
      • 表示int或float的字符串(假设为Unix时间)
  • date字段可以是:

    • date:已存在的date对象
    • int或float:请参考datetime字段。
    • str:支持如下格式:
      • YYYY-MM-DD
      • 表示int或float的字符串
  • timedelta字段可以是:

    • timedelta:已存在的timedelta对象
    • int或float:假定为秒
    • str:支持如下格式:
      • [HH:MM]SS[.ffffff]
      • [[±]P[DD]DT[HH]H[MM]M[SS]S (ISO 8601 格式的 timedelta)
from datetime import date, datetime, time, timedelta
from pydantic import BaseModel


class Model(BaseModel):
    d: date = None
    dt: datetime = None
    t: time = None
    td: timedelta = None


m = Model(
    d=1966280412345.6789,
    dt='2032-04-23T10:20:30.400+02:30',
    t=time(4, 8, 16),
    td='P3DT12H30M5S'
)

m.dict()
# {'d': datetime.date(2032, 4, 22),
#  'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000, tzinfo=datetime.timezone(datetime.timedelta(seconds=9000))),
#  't': datetime.time(4, 8, 16), 'td': datetime.timedelta(days=3, seconds=45005)}

Exotic类型

Pydantic附带了许多用于解析或验证公共对象的实用工具 。

import uuid
from decimal import Decimal
from ipaddress import IPv4Address, IPv6Address, IPv4Interface, IPv6Interface, IPv4Network, IPv6Network
from pathlib import Path
from uuid import UUID

from pydantic import (DSN, UUID1, UUID3, UUID4, UUID5, BaseModel, DirectoryPath, EmailStr, FilePath, NameEmail,
                      NegativeFloat, NegativeInt, PositiveFloat, PositiveInt, PyObject, UrlStr, conbytes, condecimal,
                      confloat, conint, constr, IPvAnyAddress, IPvAnyInterface, IPvAnyNetwork, SecretStr, SecretBytes)


class Model(BaseModel):
    cos_function: PyObject = None

    path_to_something: Path = None
    path_to_file: FilePath = None
    path_to_directory: DirectoryPath = None

    short_bytes: conbytes(min_length=2, max_length=10) = None
    strip_bytes: conbytes(strip_whitespace=True)

    short_str: constr(min_length=2, max_length=10) = None
    regex_str: constr(regex='apple (pie|tart|sandwich)') = None
    strip_str: constr(strip_whitespace=True)

    big_int: conint(gt=1000, lt=1024) = None
    mod_int: conint(multiple_of=5) = None
    pos_int: PositiveInt = None
    neg_int: NegativeInt = None

    big_float: confloat(gt=1000, lt=1024) = None
    unit_interval: confloat(ge=0, le=1) = None
    mod_float: confloat(multiple_of=0.5) = None
    pos_float: PositiveFloat = None
    neg_float: NegativeFloat = None

    email_address: EmailStr = None
    email_and_name: NameEmail = None

    url: UrlStr = None

    password: SecretStr = None
    password_bytes: SecretBytes = None

    db_name = 'foobar'
    db_user = 'postgres'
    db_password: str = None
    db_host = 'localhost'
    db_port = '5432'
    db_driver = 'postgres'
    db_query: dict = None
    dsn: DSN = None
    decimal: Decimal = None
    decimal_positive: condecimal(gt=0) = None
    decimal_negative: condecimal(lt=0) = None
    decimal_max_digits_and_places: condecimal(max_digits=2, decimal_places=2) = None
    mod_decimal: condecimal(multiple_of=Decimal('0.25')) = None
    uuid_any: UUID = None
    uuid_v1: UUID1 = None
    uuid_v3: UUID3 = None
    uuid_v4: UUID4 = None
    uuid_v5: UUID5 = None
    ipvany: IPvAnyAddress = None
    ipv4: IPv4Address = None
    ipv6: IPv6Address = None
    ip_vany_network: IPvAnyNetwork = None
    ip_v4_network: IPv4Network = None
    ip_v6_network: IPv6Network = None
    ip_vany_interface: IPvAnyInterface = None
    ip_v4_interface: IPv4Interface = None
    ip_v6_interface: IPv6Interface = None

m = Model(
    cos_function='math.cos',
    path_to_something='/home',
    path_to_file='/home/file.py',
    path_to_directory='home/projects',
    short_bytes=b'foo',
    strip_bytes=b'   bar',
    short_str='foo',
    regex_str='apple pie',
    strip_str='   bar',
    big_int=1001,
    mod_int=155,
    pos_int=1,
    neg_int=-1,
    big_float=1002.1,
    mod_float=1.5,
    pos_float=2.2,
    neg_float=-2.3,
    unit_interval=0.5,
    email_address='Samuel Colvin <[email protected] >',
    email_and_name='Samuel Colvin <[email protected] >',
    url='http://example.com',
    password='password',
    password_bytes=b'password2',
    decimal=Decimal('42.24'),
    decimal_positive=Decimal('21.12'),
    decimal_negative=Decimal('-21.12'),
    decimal_max_digits_and_places=Decimal('0.99'),
    mod_decimal=Decimal('2.75'),
    uuid_any=uuid.uuid4(),
    uuid_v1=uuid.uuid1(),
    uuid_v3=uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org'),
    uuid_v4=uuid.uuid4(),
    uuid_v5=uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org'),
    ipvany=IPv4Address('192.168.0.1'),
    ipv4=IPv4Address('255.255.255.255'),
    ipv6=IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
    ip_vany_network=IPv4Network('192.168.0.0/24'),
    ip_v4_network=IPv4Network('192.168.0.0/24'),
    ip_v6_network=IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'),
    ip_vany_interface=IPv4Interface('192.168.0.0/24'),
    ip_v4_interface=IPv4Interface('192.168.0.0/24'),
    ip_v6_interface=IPv6Interface('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128')
)
print(m.dict())
"""
{
    'cos_function': <built-in function cos>,
    'path_to_something': PosixPath('/home'),
    'path_to_file': PosixPath('/home/file.py'),
    'path_to_directory': PosixPath('/home/projects'),
    'short_bytes': b'foo',
    'strip_bytes': b'bar',
    'short_str': 'foo',
    'regex_str': 'apple pie',
    'strip_str': 'bar',
    'big_int': 1001,
    'mod_int': 155,
    'pos_int': 1,
    'neg_int': -1,
    'big_float': 1002.1,
    'mod_float': 1.5,
    'pos_float': 2.2,
    'neg_float': -2.3,
    'unit_interval': 0.5,
    'email_address': '[email protected]',
    'email_and_name': <NameEmail("Samuel Colvin <[email protected]>")>,
    'url': 'http://example.com',
    'password': SecretStr('**********'),
    'password_bytes': SecretBytes(b'**********'),
    ...
    'dsn': 'postgres://postgres@localhost:5432/foobar',
    'decimal': Decimal('42.24'),
    'decimal_positive': Decimal('21.12'),
    'decimal_negative': Decimal('-21.12'),
    'decimal_max_digits_and_places': Decimal('0.99'),
    'mod_decimal': Decimal('2.75'),
    'uuid_any': UUID('ebcdab58-6eb8-46fb-a190-d07a33e9eac8'),
    'uuid_v1': UUID('c96e505c-4c62-11e8-a27c-dca90496b483'),
    'uuid_v3': UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e'),
    'uuid_v4': UUID('22209f7a-aad1-491c-bb83-ea19b906d210'),
    'uuid_v5': UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d'),
    'ipvany': IPv4Address('192.168.0.1'),
    'ipv4': IPv4Address('255.255.255.255'),
    'ipv6': IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
    'ip_vany_network': IPv4Network('192.168.0.0/24'),
    'ip_v4_network': IPv4Network('192.168.0.0/24'),
    'ip_v6_network': IPv4Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128'),
    'ip_vany_interface': IPv4Interface('192.168.0.0/24'),
    'ip_v4_interface': IPv4Interface('192.168.0.0/24'),
    'ip_v6_interface': IPv6Interface('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128')
}
"""

字段也可以是 Callable 类型:

from typing import Callable
from pydantic import BaseModel

class Foo(BaseModel):
    callback: Callable[[int], int]

m = Foo(callback=lambda x: x)
print(m)
# Foo callback=<function <lambda> at 0x7f16bc73e1e0>

#警告:Callable 字段只执行参数是否可调用的简单检查,不执行参数	、参数的类型或返回类型的验证。

Secret类型

可以使用 SecretStrSecretBytes 数据类型来存储您不希望在日志记录或回溯中可见的敏感信息。SecretStrSecretBytes 将在转换为JSON时被格式化为 ********** 或空

from typing import List
from pydantic import BaseModel, SecretStr, SecretBytes, ValidationError


class SimpleModel(BaseModel):
    password: SecretStr
    password_bytes: SecretBytes


sm = SimpleModel(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')
print(sm)
# SimpleModel password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm.password.get_secret_value())
# IAmSensitive
print(sm.password_bytes.get_secret_value())
b'IAmSensitiveBytes'
try:
    SimpleModel(password=[1, 2, 3], password_bytes=[1, 2, 3])
except ValidationError as e:
    print(e)

# 2 validation errors
# password
#   str type expected (type=type_error.str)
# password_bytes
#   byte type expected (type=type_error.bytes)

JSON类型

可以使用JSON数据类型:Pydantic将首先解析原始JSON字符串 ,然后根据定义的JSON结构验证已解析的对象(如果提供了该对象) 。

from typing import List
from pydantic import BaseModel, Json, ValidationError


class SimpleJsonModel(BaseModel):
    json_obj: Json


class ComplexJsonModel(BaseModel):
    json_obj: Json[List[int]]


SimpleJsonModel(json_obj='{"b": 1}')
# <SimpleJsonModel json_obj={'b': 1}>
ComplexJsonModel(json_obj='[1, 2, 3]')
# <ComplexJsonModel json_obj=[1, 2, 3]>
try:
    ComplexJsonModel(json_obj=12)
except ValidationError as e:
    print(e)

# 1 validation error
# json_obj
# JSON object must be str, bytes or bytearray (type=type_error.json)
try:
    ComplexJsonModel(json_obj='[a, b]')
except ValidationError as e:
    print(e)

#
本文版权归趣快排www.sEoguruBlog.com 所有,如有转发请注明来出,竞价开户托管,seo优化请联系QQ✈61910465