pydanticを使ってpythonで型のチェックをする
Nov 18, 2023 22:40 · 1675 words · 4 minute read
とあるツールのコードを見ていたらpydanticというライブラリを使っていました。 調べてみるととても便利そうだったので、学んだことをブログにアウトプットします。
目次
pydanticでできること
pydanticは、Python実行時に型のチェックをしてくれます。 データの型を指定することで、動的型付け言語であるPythonでも型チェックの恩恵を受けることができます。
dataclassと似ていますが、dataclassは期待する型を示すのみであり、実際のデータとして違う型が与えられてもエラーは発生しません。 pydanticでは型が違う場合にエラーで検知したり、組み込みのvalidatorで値をチェックできます。
公式ドキュメント
Welcome to Pydantic - Pydantic
インストール
$ pip install pydantic
email 用のvaridatorもあるそうです。下記のいずれかのコマンドでインストールします。
$ pip install pydantic[email]
$ pip install email-validator
サンプルコード
from pydantic import BaseModel
from typing import List
from datetime import datetime
class Item(BaseModel):
id: int
name: str
release_date: datetime = None
category: List[int] = []
item_dict = {
'id': 1,
'name': "apple",
'release_date': datetime.now(),
'category': [1, 2, 3]
}
# parse.object()はpydanticv2で非推奨となった
# item = Item.parse_obj(item_dict)
item = Item(**item_dict)
print(item.id)
print(item.name)
print(item.release_date)
print(item.category)
idをaにすると、エラーが発生します。
pydantic_core._pydantic_core.ValidationError: 1 validation error for Item id Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value=‘a’, input_type=str]
idを「’id’: ‘2’」とした場合は、値をよしなにintに変換してくれます。
item_dict = {
'id': '2',
'name': "apple",
'release_date': datetime.now(),
'category': [1, 2, 3]
}
item = Item(**item_dict)
print(item.id)
関数の引数や戻り値の型として指定することもできます。 コードが見やすくなりますね。
from pydantic import BaseModel
from typing import List
from datetime import datetime
class Item(BaseModel):
id: int
name: str
release_date: datetime = None
category: List[int] = []
item_dict = {
'id': 1,
'name': "apple",
'release_date': datetime.now(),
'category': [1, 2, 3]
}
class Report(BaseModel):
items: List[Item] = []
def item_check(item: Item) -> Report:
return Report(items=[item])
item = Item(**item_dict, strict=True)
report = item_check(item)
print(report)
ただ、関数から値を返す場合はpydanticのvalidatorが実行されるわけではないので、違う値を返したとしてもエラーにはなりません。 このあたりをチェックする場合はmypyを使うのが良さそうです。
def item_check(item: Item) -> Report:
#return Report(items=[item])
return True
mypyの場合に出力されるエラー
error: Incompatible return value type (got "bool", expected "Report") [return-value]
validator
field_validator(), model_validator()を使って値のチェックができます。
pydanticのv1ではvaridator()とroot_validator()でしたが、これらの関数はv2で非推奨になりました。
from pydantic import BaseModel, field_validator, model_validator
supported_items: set = {'apple', 'orange', 'banana'}
class Item(BaseModel):
name: str
high_price: int
low_price: int
@field_validator('name')
def check_name(cls, v):
if v not in supported_items:
raise ValueError(f'The input for name {v} is not supported.')
return v
@field_validator('high_price')
def check_hight_price(cls, v):
if v > 100:
raise ValueError('The input for high_price should be greater than 100.')
return v
@model_validator(mode='before')
def check_low_high(cls, values):
if values['low_price'] > values['high_price']:
raise ValueError('The input for low_price should be less than high_price.')
return values
item_dict = {
'name': 'apple',
'high_price': 100,
'low_price': 50
}
item = Item(**item_dict)
print(item)
jsonデータの読み書き
まずはjsonの書き出しから。 model_dump_json()を使うことでpydanticのmodelをjsonとして出力できます。 json()は非推奨になり、model_dump_json()の使用が推奨されています。
from pydantic import BaseModel, field_validator, model_validator
from pathlib import Path
supported_items: set = {'apple', 'orange', 'banana'}
class Item(BaseModel):
name: str
high_price: int
low_price: int
@field_validator('name')
def check_name(cls, v):
if v not in supported_items:
raise ValueError(f'The input for name {v} is not supported.')
return v
@field_validator('high_price')
def check_hight_price(cls, v):
if v > 100:
raise ValueError('The input for high_price should be greater than 100.')
return v
@model_validator(mode='before')
def check_low_high(cls, values):
if values['low_price'] > values['high_price']:
raise ValueError('The input for low_price should be less than high_price.')
return values
item_dict = {
'name': 'apple',
'high_price': 100,
'low_price': 50
}
item = Item(**item_dict)
fpath = Path('sample.json')
fpath.write_text(item.model_dump_json())
続いてjsonの読み込み。 parse_file()はpydantic v2で非推奨になったので、将来的に使えなくなります。 代わりにmodel_validate_json()を使うことが推奨されていますが、この場合はファイルのデータを一度jsonライブラリで取り出す必要があります。 今まではparse_file()一発で読み込めていたので、ひと手間増えるのは少し残念ですね。
from pydantic import BaseModel, field_validator, model_validator
from pathlib import Path
supported_items: set = {'apple', 'orange', 'banana'}
class Item(BaseModel):
name: str
high_price: int
low_price: int
@field_validator('name')
def check_name(cls, v):
if v not in supported_items:
raise ValueError(f'The input for name {v} is not supported.')
return v
@field_validator('high_price')
def check_hight_price(cls, v):
if v > 100:
raise ValueError('The input for high_price should be greater than 100.')
return v
@model_validator(mode='before')
def check_low_high(cls, values):
if values['low_price'] > values['high_price']:
raise ValueError('The input for low_price should be less than high_price.')
return values
fpath = Path('sample.json')
item = Item.parse_file(fpath)
print(item)
最後に
pydanticについて調べ、サンプルコードを書いて動かしてみました。 とても便利だと感じたので、今後の個人開発でも使おうと思います。 今回は試していませんが、SQLAlchemyとも相性が良さそうなので、こちらも試してみたいですね。