主頁 > 後端開發 > Pydantic

Pydantic

2020-09-20 02:19:50 後端開發

目錄

  • 基本原理
  • 安裝
  • 用法
  • 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 = https://www.cnblogs.com/fengqiang626/p/{'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 = https://www.cnblogs.com/fengqiang626/p/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=https://www.cnblogs.com/fengqiang626/p/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=https://www.cnblogs.com/fengqiang626/p/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)

# 1 validation error
# json_obj
#   Invalid JSON (type=value_error.json)
try:
    ComplexJsonModel(json_obj='["a", "b"]')
except ValidationError as e:
    print(e)

# 2 validation errors
# json_obj -> 0
#   value is not a valid integer (type=type_error.integer)
# json_obj -> 1
#   value is not a valid integer (type=type_error.integer)

自定義資料型別

你也可以定義你自己的資料型別,類方法 __get_validators__ 將會被呼叫,用以獲取驗證器來決議和驗證資料,

注意:從v0.17 開始,__get_validators__ 變更成 get_validators,原來的名稱仍然可以使用,但是在將來的版本中可能會被移除,
from pydantic import BaseModel, ValidationError


class StrictStr(str):
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        if not isinstance(v, str):
            raise ValueError(f'strict string: str expected not {type(v)}')
        return v


class Model(BaseModel):
    s: StrictStr


Model(s='hello world')
# <Model s='hello world'>
try:
    print(Model(s=123))
except ValidationError as e:
    print(e.json())

# [
#   {
#     "loc": [
#       "s"
#     ],
#     "msg": "strict string: str expected not <class 'int'>",
#     "type": "value_error"
#   }
# ]

幫助函式

Pydantic在模型中提供了3個幫助函式來決議資料,這三個幫助函式都是類方法,

  • parse_obj:這個方法幾乎與模型的 init 方法相同,除過當傳遞的物件不是 dict,將會引發 ValidationError(而不是Python引發 TypeError),
  • parse_raw:接收一個字串或位元組物件并決議成JSON,或者使用 pickle 將其反序列化然后傳遞給 parse_obj 方法,傳遞的資料的型別通過 content_type 引數來推斷,否則假定為JSON,
  • parse_file:讀取檔案并將其內容傳遞給 parse_raw 方法,如果未指定 content_type 引數的值,將會由檔案拓展名來推斷檔案內容所表示的Python物件的型別,
import pickle
from datetime import datetime
from pydantic import BaseModel, ValidationError


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


m = User.parse_obj({'id': 123, 'name': 'bob'})
# <User id=123 name='bob' signup_ts=None>
try:
    User.parse_obj(['not', 'a', 'dict'])
except ValidationError as e:
    print(e)

# 1 validation error
# __obj__
#   User expected dict not list (type=type_error)
m = User.parse_raw('{"id": 123, "name": "James"}')
# <User id=123 name='James' signup_ts=None>
# pickle_data = https://www.cnblogs.com/fengqiang626/p/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)
# <User id=123 name='James' signup_ts=datetime.datetime(2017, 7, 14, 0, 0)>

# 注意:pickle 允許對復雜物件進行編碼,要使用它,需要顯式地將決議函式的 allow_pickle 引數的值設定為 True,

模型的 Config 類

Pydantic的行為可以通過模型中的 Config 類來控制,

Config 類包含如下一些類屬性:

  • anystr_strip_whitespace:是否消除字串或者位元組的前導和后置空白空格,默認為 False,
  • min_anystr_length:字串或位元組的最小長度,默認為0
  • max_anystr_length:字串或位元組的最大長度,默認為 2 ** 16
  • validate_all:是否驗證欄位的默認值,默認為 False,
  • extra:是否忽略、允許或禁止模型中的額外屬性,可以使用字串值 ignor,allow或 forbid,也可以使用 Extra 的列舉值,默認值是 Extra.ignore,
  • allow_mutation:模型是否為偽不可變型別,默認值為 True,
  • use_enum_values:是否使用列舉的 value 特性填充模型,而不是使用原始列舉,這在您希望序列化 model.dict() 時非常有用,默認值為 False,
  • fields:每個欄位的模式資訊,這等價于使用 schema 類,默人在 None,
  • validate_assignment:是否在為屬性賦值時執行驗證,默認為 False,
  • allow_population_by_alias:是否可以按照模型屬性(而不是嚴格的別名)給出的名稱填充別名欄位;在啟用此功能之前,請務必閱讀下面的警告,默認值為 False,
    error_msg_templates:用于重寫默認的錯誤訊息模版,傳入一個字典,其中的鍵與要覆寫的錯誤訊息匹配,默認值為空的字典,
  • arbitrary_types_allowed:是否允許欄位使用任意的用戶自定義資料型別(只需檢查值是否是該型別的實體即可驗證它們),如果該引數值為 False,在模型宣告中使用自定義的資料型別將會引發 RuntimeError 例外,默認值為 False,
  • json_encoders:定制將型別編碼為JSON的方式,請參閱JSON序列化了解更多細節,
警告:在啟用 allow_population_by_alias 之前請三思!啟用它可能會導致先前正確的代碼變得不正確,例如,假設您有一個名為 card_number 的欄位和別名 cardNumber,在 allow_population_by_alias 屬性值為 False 時,只使用鍵 card_number 嘗試決議物件將會失敗,但是,如果 allow_population_by_alias 屬性值設定為 True,那么現在可以從 cardNumber 或 card_number 填充 card_number 欄位,并且先前無效的示例物件現在將是有效的,對于某些用例,這可能是需要的,但是在其他用例中(比如這里給出的例子),放松對別名的嚴格限制可能會引入bug,
from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    v: str

    class Config:
        max_anystr_length = 10
        error_msg_templates = {'value_error.any_str.max_length': 'max_length:{limit_value}'}


try:
    Model(v='x' * 20)
except ValidationError as e:
    print(e)

# 1 validation error
# v
#   max_length:10 (type=value_error.any_str.max_length; limit_value=https://www.cnblogs.com/fengqiang626/p/10)

基于 @dataclass 裝飾器版本的模型:

from datetime import datetime
from pydantic import ValidationError
from pydantic.dataclasses import dataclass


class MyConfig:
    max_anystr_length = 10
    validate_assignment = True
    error_msg_templates = {
        'value_error.any_str.max_length': 'max_length:{limit_value}',
    }


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


user = User(id='42', signup_ts='2032-06-21T12:00')
try:
    user.name = 'x' * 20
except ValidationError as e:
    print(e)

# 1 validation error
# name
#   max_length:10 (type=value_error.any_str.max_length; limit_value=https://www.cnblogs.com/fengqiang626/p/10)

設定

Pydantic最有用的應用之一是定義默認設定,并允許它們被環境變數或關鍵字引數覆寫(例如在單元測驗中),

from typing import Set
from pydantic import BaseModel, DSN, BaseSettings, PyObject


class SubModel(BaseModel):
    foo = 'bar'
    apple = 1


class Settings(BaseSettings):
    redis_host = 'localhost'
    redis_port = 6379
    redis_database = 0
    redis_password: str = None
    auth_key: str = ...
    invoicing_cls: PyObject = 'path.to.Invoice'
    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
    # to override domains:
    # export MY_PREFIX_DOMAINS = '["foo.com", "bar.com"]'
    domains: Set[str] = set()
    # to override more_settings:
    # export MY_PREFIX_MORE_SETTINGS = '{"foo": "x", "apple": 1}'
    more_settings: SubModel = SubModel()

    class Config:
        env_prefix = 'MY_PREFIX_'  # defaults to 'APP_'
        fields = {
            'auth_key': {
                'alias': 'my_api_key'
            }
        }

這里的 redis_port 可以通過 export MY_PREFIX_REDIS_PORT=6380 修改,auth_key 可以通過 exportmy_api_key=6380 修改,

默認情況下,BaseSettings 按照以下優先級來獲取欄位值(其中3的優先級最高,并會重寫其他兩項):

  • Settings 中設定的值,

  • 環境變數中設定的值,例如上面的 MY_PREFIX_REDIS_PORT

  • 初始化 Settings 類時傳遞的引數,

可以通過重寫 BaseSettings 類的 _build_values 方法來更改這個默認行為,

復雜的型別,如 listdictset 和子模型可以通過JSON環境變數來設定,

可以以不區分大小寫的方式讀取環境變數:

from pydantic import BaseSettings

class Settings(BaseSettings):
  redis_host = 'localhost'
  class Config:
    case_insensitive = True

這里的 redis_port 可以通過 export APP_REDIS_HOSTexport app_redis_hostexport app_REDIS_host 修改,

動態模型創建

在某些情況下,模型的形狀直到運行時才知道,由于這個原因,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

這里,DynamicFoobarModelStaticFoobarModel 是完全相同的,

欄位要么由表單的一個元組 (<type>, <default value>) 定義,要么僅由一個默認值定義,特殊的關鍵字引數 __config____base__ 可以用來定制新模型,這包括使用額外的欄位擴展基本模型,

from pydantic import BaseModel, create_model


class FooModel(BaseModel):
    foo: str
    bar: int = 120


BarModel = create_model('BarModel', apple='russet', banana='yellow', __base__=FooModel)
# <class 'BarModel'>
# ', '.join(BarModel.__fields__.keys())
# 'foo, bar, apple, banana'

與mypy一起使用

Pydantic和mypy一起作業,可以讓你使用所需變數的 僅注釋 版本,

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, NoneStr


class Model(BaseModel):
    age: int
    first_name = 'John'
    last_name: NoneStr = None
    signup_ts: Optional[datetime] = None
    list_of_ints: List[int]
    

m = Model(age=42, list_of_ints=[1, '2', b'3'])
try:
    Model()
except ValidationError as e:
    print(e)

# 2 validation errors
# age
#   field required (type=value_error.missing)

也可以通過mypy以如下方式運行:

mypy --ignore-missing-imports --follow-imports=skip --strict-optional pydantic_mypy_test.py

嚴格的可選項

要給代碼傳遞 --strict-optional 選項,需要對所有沒有預設值的欄位使用 Optional[] 或 Optional[] 的別名,這是mypy的標準做法,

Pydantic提供了一些有用的可選或聯合型別:

  • NoneStr 又叫做 Optional[str]
  • NoneBytes 又叫做 Optional[bytes]
  • StrBytes 又叫做 Union[str, bytes]
  • NoneStrBytes 又叫做 Optional[StrBytes]

如果這些還不夠,你可以定義,

必須欄位和mypy

省略號符號 不能與mypy一同作業,您需要像上面的示例那樣使用注釋欄位,

警告:請注意,僅使用注釋欄位將更改元資料中欄位的順序和錯誤:始終以僅有注釋的欄位優先,但仍然按照欄位定義的順序,

要解決這個問題,可以使用 Required(通過 pydantic import Required)欄位作為僅使用省略號或注釋的欄位的別名,

偽不可變性

可以通過 allow_mutation = False 將模型配置為不可變的,這將防止更改模型的屬性,

警告: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)

試圖更改 a 導致了一個錯誤,并且它仍然保持不變,但是字典物件 b 是可變的,foobar 的不變性不會阻止 b 的值的更改,

復制

dict 方法回傳一個包含模型屬性的字典,子模型被遞回地轉換為字典, copy 方法允許模型被復制,這對于不可變模型特別有用,

dict、copy 和 json 方法(如下所述)都使用可選的 include 和 exclude 關鍵字引數來控制回傳或復制哪些屬性,copy 方法接受額外的關鍵字引數 update,它接受將屬性映射到新值的型別為字典物件的值,這些新值將在復制模型并進行深度復制時應用,

dict 和 json 采用可選的 skip_defaults 關鍵字引數,該引數將跳過未顯式設定的屬性,這對于減少具有許多不經常更改的默認欄位的模型的序列化大小非常有用,

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': 456})
m.dict()
# {'banana': 3.14, 'foo': 'hello', 'bar': {'whatever': 456}}
m.dict(include={'foo', 'bar'})
# {'foo': 'hello', 'bar': {'whatever': 456}}
m.dict(exclude={'foo', 'bar'})
# {'banana': 3.14}
m.copy()
# <FooBarModel banana=3.14 foo='hello' bar=<BarModel whatever=456>>
m.copy(include={'foo', 'bar'})
# <FooBarModel foo='hello' bar=<BarModel whatever=456>>
m.copy(exclude={'foo', 'bar'})
# <FooBarModel banana=3.14>
m.copy(update={'banana': 0})
# <FooBarModel banana=0 foo='hello' bar=<BarModel whatever=456>>
id(m.bar), id(m.copy().bar)
# (4417630280, 4417630280)
id(m.bar), id(m.copy(deep=True).bar)
# (4417630280, 4417630928)

序列化

Pydantic支持將資料序列化為JSON和Pickle,當然可以通過處理 dict() 方法的結果將資料序列化為您喜歡的任何其他格式,

JSON序列化

json()方法將模型序列化為JSON,然后 json() 方法呼叫 dict() 方法并序列化其結果,

可以通過在模型上使用 json_encoders 配置屬性定制序列化,鍵應該是型別,值應該是序列化該型別的函式,參見下面的示例,

如果這還不夠,json() 方法接受一個可選的 encoder 引數,該引數允許完全控制如何將非標準型別編碼為JSON,

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


class BarModel(BaseModel):
    whatever: int


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


m = FooBarModel(foo=datetime(2032, 6, 1, 12, 13, 14), bar={'whatever': 400})
m.json()


# '{"foo": "2032-06-01T12:13:14", "bar": {"whatever": 400}}'
class WithCustomEncoders(BaseModel):
    dt: datetime
    diff: timedelta

    class Config:
        json_encoders = {
            datetime: lambda v: (v - datetime(1970, 1, 1)).total_seconds(),
            timedelta: timedelta_isoformat
        }


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

默認情況下,時間增量被編碼為一個簡單的浮點數,以總秒為單位,timedelta_isoformat 作為一個可選選項提供,它實作了ISO 8601時間差異編碼,

Pickle序列化

使用與 copy() 相同的管道,Pydantic支持有效的 pickleunpick

import pickle
from pydantic import BaseModel


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


m = FooBarModel(a='hello', b=100)
# <FooBarModel a='hello' b=100>
data = https://www.cnblogs.com/fengqiang626/p/pickle.dumps(m)
# b'\x80\x03c__main__\nFooBarModel\nq\x00)\x81q\x01}q\x02(X\n\x00\x00\x00__values__q\x03}q\x04(X\x01\x00\x00\x00aq\x05X\x05\x00\x00\x00helloq\x06X\x01\x00\x00\x00bq\x07KduX\x0e\x00\x00\x00__fields_set__q\x08cbuiltins\nset\nq\t]q\n(h\x07h\x05e\x85q\x0bRq\x0cub.'
m2 = pickle.loads(data)
# <FooBarModel a='hello' b=100>

抽象基類

Pydantic模型可以與Python的抽象基類(ABC)一起使用,

import abc
from pydantic import BaseModel


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

    @abc.abstractmethod
    def my_abstract_method(self):
        pass

延遲注解

注意:通過 future 匯入和 ForwardRef 的延遲注解都需要Python 3.7+,

全域延遲注解在Pydantic中應該 只是能作業

from __future__ import annotations
from typing import List
from pydantic import BaseModel


class Model(BaseModel):
    a: List[int]


Model(a=('1', 2, 3))
# <Model a=[1, 2, 3]>

在內部,Pydantic將呼叫類似于 typing.get_type_hints 的方法用于決議注釋,

要使用 ForwardRef ,您可能需要在創建模型之后呼叫 model .update_forward_refs(),這是因為在下面的示例中,Foo 在創建之前并不存在(顯然),所以 ForwardRef 不能首先被決議,您必須等到 Foo 創建之后,然后呼叫 update_forward_refs 來正確地設定型別,然后才能使用模型,

from typing import ForwardRef
from pydantic import BaseModel

Foo = ForwardRef('Foo')


class Foo(BaseModel):
    a: int = 123
    b: Foo = None


Foo.update_forward_refs()
Foo()
# <Foo a=123 b=None>
Foo(b={'a': '321'})
# <Foo a=123 b=<Foo a=321 b=None>>
# 警告:要將字串(型別名稱)決議成注解(型別),pydantic需要查找 moduel.__dict__ ,就像 get_type_hints 所做的一樣 ,這意味著pydantic不能很好地處理模塊全域范圍內未定義的型別,

# 例如,這樣可以正常作業:
from __future__ import annotations
from typing import List  # <-- List is defined in the module's global scope
from pydantic import BaseModel

def this_works():
    class Model(BaseModel):
        a: List[int]
    print(Model(a=(1, 2)))
# 但是這樣將不能:
from __future__ import annotations
from pydantic import BaseModel

def this_is_broken():
    from typing import List  # <-- List is defined inside the function so is not in the module's global scope
    class Model(BaseModel):
        a: List[int]
    print(Model(a=(1, 2)))
# 解決這個問題超出了pydantic的呼叫:要么洗掉 future 的匯入,要么全域地宣告型別,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/85038.html

標籤:Python

上一篇:96不同的二查搜索樹

下一篇:136只出現一次的數字

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more