首頁 > 軟體

python庫pydantic的簡易入門教學

2022-03-28 19:00:48

一、簡介

pydantic 庫是 python 中用於資料介面定義檢查與設定管理的庫。

pydantic 在執行時強制執行型別提示,並在資料無效時提供友好的錯誤。

它具有如下優點:

  • 與 IDE/linter 完美搭配,不需要學習新的模式,只是使用型別註解定義類的範例
  • 多用途,BaseSettings 既可以驗證請求資料,也可以從環境變數中讀取系統設定
  • 快速
  • 可以驗證複雜結構
  • 可延伸,可以使用validator裝飾器裝飾的模型上的方法來擴充套件驗證
  • 資料類整合,除了BaseModel,pydantic還提供了一個dataclass裝飾器,它建立帶有輸入資料解析和驗證的普通 Python 資料類。

二、安裝

pip install pydantic

要測試 pydantic 是否已編譯,請執行:

import pydantic
print('compiled:', pydantic.compiled)

支援使用dotenv檔案獲取設定,需要安裝 python-dotenv

pip install pydantic[dotenv]

三、常見模型

pydantic中定義物件都是通過模型的,你可以認為模型就是型別語言中的型別。

1、BaseModel 基本模型

from pydantic import BaseModel
class User(BaseModel):
    id: int
    name = 'Jane Doe'

上面的例子,定義了一個User模型,繼承自BaseModel,有2個欄位,id是一個整數並且是必需的,name是一個帶有預設值的字串並且不是必需的

範例化使用:

user = User(id='123')

範例化將執行所有解析和驗證,如果有錯誤則會觸發 ValidationError 報錯。

模型具有以下屬性:

  • dict() 模型欄位和值的字典

  • json() JSON 字串表示dict()

  • copy() 模型的副本(預設為淺表副本)

  • parse_obj() 使用dict解析資料

  • parse_raw 將str或bytes並將其解析為json,然後將結果傳遞給parse_obj

  • parse_file 檔案路徑,讀取檔案並將內容傳遞給parse_raw。如果content_type省略,則從檔案的擴充套件名推斷

  • from_orm() 從ORM 物件建立模型

  • schema() 返回模式的字典

  • schema_json() 返回該字典的 JSON 字串表示

  • construct() 允許在沒有驗證的情況下建立模型

  • __fields_set__ 初始化模型範例時設定的欄位名稱集

  • __fields__ 模型欄位的字典

  • __config__ 模型的設定類

2、遞迴模型

可以使用模型本身作為註釋中的型別來定義更復雜的資料結構。

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]

3、GenericModel 通用模型(泛型):

使用 typing.TypeVar 的範例作為引數,傳遞給 typing.Generic,然後在繼承了pydantic.generics.GenericModel 的模型中使用:

from typing import Generic, TypeVar, Optional, List

from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel

DataT = TypeVar('DataT')

class Error(BaseModel):
    code: int
    message: str

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

class Response(GenericModel, Generic[DataT]):
    data: Optional[DataT]
    error: Optional[Error]

    @validator('error', always=True)
    def check_consistency(cls, v, values):
        if v is not None and values['data'] is not None:
            raise ValueError('must not provide both data and error')
        if v is None and values.get('data') is None:
            raise ValueError('must provide data or error')
        return v

data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message='Not found')

print(Response[int](data=1))
#> data=1 error=None
print(Response[str](data='value'))
#> data='value' error=None
print(Response[str](data='value').dict())
#> {'data': 'value', 'error': None}
print(Response[DataModel](data=data).dict())
"""
{
    'data': {'numbers': [1, 2, 3], 'people': []},
    'error': None,
}
"""
print(Response[DataModel](error=error).dict())
"""
{
    'data': None,
    'error': {'code': 404, 'message': 'Not found'},
}
"""
try:
    Response[int](data='value')
except ValidationError as e:
    print(e)
    """
    2 validation errors for Response[int]
    data
      value is not a valid integer (type=type_error.integer)
    error
      must provide data or error (type=value_error)
    """

4、create_model 動態模型

在某些情況下,直到執行時才知道模型的結構。為此 pydantic 提供了create_model允許動態建立模型的方法。

from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model('DynamicFoobarModel', foo=(str, ...), bar=123)

四、常用型別

  • None,type(None)或Literal[None]只允許None值

  • bool 布林型別

  • int 整數型別

  • float 浮點數型別

  • str 字串型別

  • bytes 位元組型別

  • list 允許list,tuple,set,frozenset,deque, 或生成器並轉換為列表

  • tuple 允許list,tuple,set,frozenset,deque, 或生成器並轉換為元組

  • dict 字典型別

  • set 允許list,tuple,set,frozenset,deque, 或生成器和轉換為集合;

  • frozenset 允許list,tuple,set,frozenset,deque, 或生成器和強制轉換為凍結集

  • deque 允許list,tuple,set,frozenset,deque, 或生成器和強制轉換為雙端佇列

  • datetime 的date,datetime,time,timedelta 等日期型別

  • typing 中的 Deque, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union,Callable,Pattern等型別

  • FilePath,檔案路徑

  • DirectoryPath 目錄路徑

  • EmailStr 電子郵件地址

  • NameEmail 有效的電子郵件地址或格式

  • PyObject 需要一個字串並載入可在該虛線路徑中匯入的 python 物件;

  • Color 顏色型別

  • AnyUrl 任意網址

  • SecretStr、SecretBytes 敏感資訊,將被格式化為'**********'或''

  • Json 型別

  • PaymentCardNumber 支付卡型別

  • 約束型別,可以使用con*型別函數限制許多常見型別的值

    • conlist
  • item_type: Type[T]: 列表項的型別
  • min_items: int = None: 列表中的最小專案數
  • max_items: int = None: 列表中的最大專案數
  • conset
  • item_type: Type[T]: 設定專案的型別
  • min_items: int = None: 集合中的最小專案數
  • max_items: int = None: 集合中的最大專案數
  • conint
  • strict: bool = False: 控制型別強制
  • gt: int = None: 強制整數大於設定值
  • ge: int = None: 強制整數大於或等於設定值
  • lt: int = None: 強制整數小於設定值
  • le: int = None: 強制整數小於或等於設定值
  • multiple_of: int = None: 強制整數為設定值的倍數
  • confloat
  • strict: bool = False: 控制型別強制
  • gt: float = None: 強制浮點數大於設定值
  • ge: float = None: 強制 float 大於或等於設定值
  • lt: float = None: 強制浮點數小於設定值
  • le: float = None: 強制 float 小於或等於設定值
  • multiple_of: float = None: 強制 float 為設定值的倍數
  • condecimal
  • gt: Decimal = None: 強制十進位制大於設定值
  • ge: Decimal = None: 強制十進位制大於或等於設定值
  • lt: Decimal = None: 強制十進位制小於設定值
  • le: Decimal = None: 強制十進位制小於或等於設定值
  • max_digits: int = None: 小數點內的最大位數。它不包括小數點前的零或尾隨的十進位制零
  • decimal_places: int = None: 允許的最大小數位數。它不包括尾隨十進位制零
  • multiple_of: Decimal = None: 強制十進位制為設定值的倍數
  • constr
  • strip_whitespace: bool = False: 刪除前尾空格
  • to_lower: bool = False: 將所有字元轉為小寫
  • strict: bool = False: 控制型別強制
  • min_length: int = None: 字串的最小長度
  • max_length: int = None: 字串的最大長度
  • curtail_length: int = None: 當字串長度超過設定值時,將字串長度縮小到設定值
  • regex: str = None: 正規表示式來驗證字串
  • conbytes
  • strip_whitespace: bool = False: 刪除前尾空格
  • to_lower: bool = False: 將所有字元轉為小寫
  • min_length: int = None: 位元組串的最小長度
  • max_length: int = None: 位元組串的最大長度
  • 嚴格型別,您可以使用StrictStr,StrictBytes,StrictInt,StrictFloat,和StrictBool型別,以防止強制相容型別

五、驗證器

使用validator裝飾器可以實現自定義驗證和物件之間的複雜關係。

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

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

user = UserModel(
    name='samuel colvin',
    username='scolvin',
    password1='zxcvbn',
    password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'

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)
    """

關於驗證器的一些注意事項:

  • 驗證器是“類方法”,因此它們接收的第一個引數值是UserModel類,而不是UserModel
  • 第二個引數始終是要驗證的欄位值,可以隨意命名
  • 單個驗證器可以通過傳遞多個欄位名稱來應用於多個欄位,也可以通過傳遞特殊值在所有欄位上呼叫單個驗證器'*'
  • 關鍵字引數pre將導致在其他驗證之前呼叫驗證器
  • 通過each_item=True將導致驗證器被施加到單獨的值(例如List,Dict,Set等),而不是整個物件
from typing import List
from pydantic import BaseModel, ValidationError, validator

class ParentModel(BaseModel):
    names: List[str]

class ChildModel(ParentModel):
    @validator('names', each_item=True)
    def check_names_not_empty(cls, v):
        assert v != '', 'Empty strings are not allowed.'
        return v

# This will NOT raise a ValidationError because the validator was not called
try:
    child = ChildModel(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
    print(e)
else:
    print('No ValidationError caught.')
    #> No ValidationError caught.


class ChildModel2(ParentModel):
    @validator('names')
    def check_names_not_empty(cls, v):
        for name in v:
            assert name != '', 'Empty strings are not allowed.'
        return v

try:
    child = ChildModel2(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
    print(e)
    """
    1 validation error for ChildModel2
    names
      Empty strings are not allowed. (type=assertion_error)
    """
  • 關鍵字引數 always 將導致始終驗證,出於效能原因,預設情況下,當未提供值時,不會為欄位呼叫驗證器。然而,在某些情況下,始終呼叫驗證器可能很有用或需要,例如設定動態預設值。
  • allow_reuse 可以在多個欄位/模型上使用相同的驗證器
from pydantic import BaseModel, validator

def normalize(name: str) -> str:
    return ' '.join((word.capitalize()) for word in name.split(' '))

class Producer(BaseModel):
    name: str

    # validators
    _normalize_name = validator('name', allow_reuse=True)(normalize)

class Consumer(BaseModel):
    name: str
    # validators
    _normalize_name = validator('name', allow_reuse=True)(normalize)

六、設定

如果您建立一個繼承自BaseSettings的模型,模型初始化程式將嘗試通過從環境中讀取,來確定未作為關鍵字引數傳遞的任何欄位的值。(如果未設定匹配的環境變數,則仍將使用預設值。)

這使得很容易:

  • 建立明確定義、型別提示的應用程式設定類
  • 自動從環境變數中讀取對設定的修改
  • 在需要的地方手動覆蓋初始化程式中的特定設定(例如在單元測試中)
from typing import Set

from pydantic import (
    BaseModel,
    BaseSettings,
    PyObject,
    RedisDsn,
    PostgresDsn,
    Field,
)

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

class Settings(BaseSettings):
    auth_key: str
    api_key: str = Field(..., env='my_api_key')

    redis_dsn: RedisDsn = 'redis://user:pass@localhost:6379/1'
    pg_dsn: PostgresDsn = 'postgres://user:pass@localhost:5432/foobar'

    special_function: PyObject = 'math.cos'

    # 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 no prefix, i.e. ""
        fields = {
            'auth_key': {
                'env': 'my_auth_key',
            },
            'redis_dsn': {
                'env': ['service_redis_dsn', 'redis_url']
            }
        }

print(Settings().dict())
"""
{
    'auth_key': 'xxx',
    'api_key': 'xxx',
    'redis_dsn': RedisDsn('redis://user:pass@localhost:6379/1',
scheme='redis', user='user', password='pass', host='localhost',
host_type='int_domain', port='6379', path='/1'),
    'pg_dsn': PostgresDsn('postgres://user:pass@localhost:5432/foobar',
scheme='postgres', user='user', password='pass', host='localhost',
host_type='int_domain', port='5432', path='/foobar'),
    'special_function': <built-in function cos>,
    'domains': set(),
    'more_settings': {'foo': 'bar', 'apple': 1},
}
"""

支援 Dotenv 檔案設定變數,pydantic 有兩種方式載入它:

class Settings(BaseSettings):
    ...

    class Config:
        env_file = '.env'
        env_file_encoding = 'utf-8'

或者

settings=Settings(_env_file='prod.env',_env_file_encoding='utf-8')

即使使用 dotenv 檔案,pydantic 仍會讀取環境變數,環境變數將始終優先於從 dotenv 檔案載入的值。

pydantic 支援設定敏感資訊檔案,同樣有2種方式載入:

class Settings(BaseSettings):
    ...
    database_password: str
    class Config:
        secrets_dir = '/var/run'

或者:

settings = Settings(_secrets_dir='/var/run')

即使使用 secrets 目錄,pydantic仍會從 dotenv 檔案或環境中讀取環境變數,dotenv 檔案和環境變數將始終優先於從 secrets 目錄載入的值。

七、與 mypy 一起使用

Pydantic 附帶了一個 mypy 外掛,向 mypy 新增了許多重要的特定於 pydantic 的功能,以提高其對程式碼進行型別檢查的能力。

例如以下指令碼:

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'])
print(m.middle_name)  # not a model field!
Model()  # will raise a validation error for age and list_of_ints

在沒有任何特殊設定的情況下,mypy 會捕獲其中一個錯誤:

13: error: "Model" has no attribute "middle_name"

啟用外掛後,它會同時捕獲:

13: error: "Model" has no attribute "middle_name"
16: error: Missing named argument "age" for "Model"
16: error: Missing named argument "list_of_ints" for "Model"

要啟用該外掛,只需新增pydantic.mypy到mypy 組態檔中的外掛列表:

[mypy]
plugins = pydantic.mypy

要更改外掛設定的值,請​​在 mypy 組態檔中建立一個名為 的部分[pydantic-mypy],併為要覆蓋的設定新增鍵值對:

[mypy]
plugins = pydantic.mypy

follow_imports = silent
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True

[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True
warn_untyped_fields = True

總結

到此這篇關於python庫pydantic簡易入門的文章就介紹到這了,更多相關python庫pydantic教學內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com