Pydantic使用

403·Forbidden發表於2021-12-29

Pydantic可以在程式碼執行時提供型別提示, 資料校驗失敗時提供友好的錯誤提示, 使用Python的型別註解來進行資料校驗和settings管理

一般使用

from datetime import datetime
from typing import List
from typing import Optional

from pydantic import BaseModel


# 1 定義模型
class User(BaseModel):
    id: int  # 必須欄位
    name: str = "John Snow"  # 有預設值,選填欄位
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表中元素是int型別或者可以直接轉換成int型別


external_data = {
    "id": "123",
    "signup_ts": "2020-12-22 12:22",
    "friends": [1, 2, "3"],  # "3"是可以int("3")的
}
# 2 檢驗資料
user = User(**external_data)
# 3 訪問資料
print(user.id, user.friends)  # 例項化後呼叫屬性
print(repr(user.signup_ts))
# .dict 方法, 返回資料字典
print(user.dict())

校驗失敗處理

假如資料檢驗不通過, 會丟擲pydantic.ValidationError

from datetime import datetime

from typing import List
from typing import Optional

from pydantic import BaseModel, ValidationError


class User(BaseModel):
    id: int  # 必須欄位
    name: str = "John Snow"  # 有預設值,選填欄位
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表中元素是int型別或者可以直接轉換成int型別


external_data = {
    "id": "123",
    "signup_ts": "2020-12-22 12:22",
    "friends": [1, 2, "3"],  # "3"是可以int("3")的
}

try:
    User(id=1, signup_ts=datetime.today(), friends=[1, 2, "not number"])
except ValidationError as e:
    print(e.json())
    """
    [
      {
        "loc": [
          "friends",
          2
        ],
        "msg": "value is not a valid integer",
        "type": "type_error.integer"
      }
    ]
    """

模型類的的屬性和方法

from datetime import datetime
from pathlib import Path
from typing import List
from typing import Optional

from pydantic import BaseModel


class User(BaseModel):
    id: int  # 必須欄位
    name: str = "John Snow"  # 有預設值,選填欄位
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表中元素是int型別或者可以直接轉換成int型別


external_data = {
    "id": "123",
    "signup_ts": "2020-12-22 12:22",
    "friends": [1, 2, "3"],  # "3"是可以int("3")的
}
user = User(**external_data)

# 獲得已檢驗資料的字典資料
print(user.dict())

# 獲得已檢驗資料的json資料
print(user.json())

# 這裡是淺拷貝
print(user.copy())

# 通過物件解析
print(User.parse_obj(external_data))

# 通過字串解析
print(User.parse_raw('{"id": "123", "signup_ts": "2020-12-22 12:22", "friends": [1, 2, "3"]}'))

path = Path('pydantic_tutorial.json')
path.write_text('{"id": "123", "signup_ts": "2020-12-22 12:22", "friends": [1, 2, "3"]}')
# 通過文字解析
print(User.parse_file(path))

# 獲得物件 概要
# {'title': 'User', 'type': 'object', 'properties': {'id': ...} }
print(user.schema())
# 獲得物件 概要json
# {'title': 'User', 'type': 'object', 'properties': {'id': ...} }
print(user.schema_json())

user_data = {"id": "error", "signup_ts": "2020-12-22 12 22", "friends": [1, 2, 3]}  # id是字串 是錯誤的
# 不檢驗資料直接建立模型類,不建議在construct方法中傳入未經驗證的資料
print(User.construct(**user_data))

# 獲得所有欄位
# 定義模型類的時候,所有欄位都註明型別,欄位順序就不會亂
print(User.__fields__.keys())

模型巢狀

from datetime import datetime, date

from typing import List
from typing import Optional

from pydantic import BaseModel


class User(BaseModel):
    id: int  # 必須欄位
    name: str = "John Snow"  # 有預設值,選填欄位
    signup_ts: Optional[datetime] = None
    friends: List[int] = []  # 列表中元素是int型別或者可以直接轉換成int型別


external_data = {
    "id": "123",
    "signup_ts": "2020-12-22 12:22",
    "friends": [1, 2, "3"],  # "3"是可以int("3")的
}
user = User(**external_data)


class Sound(BaseModel):
    sound: str


class Dog(BaseModel):
    birthday: date
    weight: float = Optional[None]
    sound: List[Sound]  # 不同的狗有不同的叫聲。遞迴模型(Recursive Models)就是指一個巢狀一個


dogs = Dog(birthday=date.today(), weight=6.66, sound=[{"sound": "wang wang ~"}, {"sound": "ying ying ~"}])
print(dogs.dict())

與ORM結合

from datetime import datetime

from typing import List
from typing import Optional

from pydantic import BaseModel
from pydantic import constr
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'
    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    id: int
    # constr用於約束字串
    public_key: constr(max_length=20)
    name: constr(max_length=63)
    domains: List[constr(max_length=255)]

    class Config:
        orm_mode = True


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)

print(CompanyModel.from_orm(co_orm))

驗證器

如何使用及引數見passwords_match方法

from pydantic import BaseModel, ValidationError, validator


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

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

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        """
        :param v: 當前欄位的值: zxcvbn2
        :param values: 已經驗證的資料: {'username': 'scolvin', 'password1': 'zxcvbn'}
        :param kwargs: {'field': ModelField(name='password2', type=str, required=True), 
                            'config': <class '__main__.Config'>}
        :return:
        """

        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v

    @validator('username')
    def username_alphanumeric(cls, v):
        assert v.isalnum(), 'must be alphanumeric'
        return v


try:
    UserModel(
        name='samuel',
        username='scolvin',
        password1='zxcvbn',
        password2='zxcvbn2',
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for UserModel
    name
      must contain a space (type=value_error)
    password2
      passwords do not match (type=value_error)
    """

全部欄位型別

見官方文件: Field Types

更多其他使用方法見: pydantic-docs

相關文章