Pydantic 是一個使用Python類型提示來進行數據驗證和設置管理的庫。Pydantic定義數據應該如何使用純Python規範用併進行驗證。PEP 484 從Python3.5開始引入了類型提示的功能,PEP 526 使用Python3.6中的變數註釋語法對其進行了拓展。Pydantic使用這 ...
目錄
- 基本原理
- 安裝
- 用法
- dataclasses
- 選擇
- 驗證器
- 遞歸模型
- 模式創建
- 錯誤處理
- datetime類型
- Exotic類型
- Secret類型
- JSON類型
- 自定義數據類型
- 幫助函數
- 模型的 Config 類
- 設置
- 動態模型創建
- 與mypy一起使用
- 偽不可變性
- 複製
- 序列化
- 抽象基類
- 延遲註解
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"
# }
# ]
如果你自定義了數據類型或者驗證器,你應該使用 TypeError 和 ValueError 引發錯誤:
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類型
可以使用 SecretStr
和 SecretBytes
數據類型來存儲您不希望在日誌記錄或回溯中可見的敏感信息。SecretStr
和 SecretBytes
將在轉換為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 = 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=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=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
方法來更改這個預設行為。
複雜的類型,如 list
,dict
,set
和子模型可以通過JSON環境變數來設置。
可以以不區分大小寫的方式讀取環境變數:
from pydantic import BaseSettings
class Settings(BaseSettings):
redis_host = 'localhost'
class Config:
case_insensitive = True
這裡的 redis_port
可以通過 export APP_REDIS_HOST
, export app_redis_host
或 export 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
這裡,DynamicFoobarModel
和 StaticFoobarModel
是完全相同的。
欄位要麼由表單的一個元組 (<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支持有效的 pickle 和 unpick。
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 = 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 的導入,要麼全局地聲明類型。