Open2
Pydanticことはじめ
Pythonで型を使ったコードは、せいぜい型ヒントぐらいで、恥ずかしながらPydanticをちゃんと使ったことがなかったので、やってみる。
インストール
Colaboratoryで。
まずColaboratoryデフォのPydanticのバージョンを確認。
!pip show pydantic
1.10.3と古い。Pydanticの最新は執筆時点で2.5.3。
Name: pydantic
Version: 1.10.13
Summary: Data validation and settings management using python type hints
(snip)
ということでアップグレード。
!pip install -U pydantic
!pip show pydantic
Name: pydantic
Version: 2.5.3
Summary: Data validation using Python type hints
(snip)
Getting Started
PydanticのBaseModelを継承してDelivery
というデータモデルのクラスを定義する。
Deliveryクラスは、datetime型のtimestamp
と2つのIntを持つTuple型のdimensions
という属性を持たせている。
from datetime import datetime
from typing import Tuple
from pydantic import BaseModel
class Delivery(BaseModel):
timestamp: datetime
dimensions: Tuple[int, int]
ではDelivery
クラスのインスタンスを作成する。クラス定義のとおりに属性を指定してみる。
m = Delivery(timestamp=datetime.now(), dimensions=(10, 20))
でそれぞれの属性を見てみる。
print(repr(m.timestamp))
print(repr(m.dimensions))
datetime.datetime(2024, 1, 12, 13, 0, 11, 299111)
(10, 20)
ではもう一つインスタンスを作成して、少し属性の指定を変えてみる。
m2 = Delivery(timestamp='2024-01-12T21:45:05+0900', dimensions=['10.0', '20.0'])
print(repr(m2.timestamp))
print(repr(m2.dimensions))
datetime.datetime(2024, 1, 12, 21, 45, 5, tzinfo=TzInfo(+09:00))
(10, 20)
日付の文字列表現をdatetime型に変換してくれているし、文字列かつリストで渡した値もきちんとIntのTupleに変換してくれている。
なるほど、このように出力に型と制約を保証してくれるというものなのか。たしかに便利。
とはいえ、流石に無理なものは無理なので、こういうのは当然エラーになる。
m2 = Delivery(timestamp='2024-01-12T21:45:05+0900', dimensions=['あああ', '20.0'])
ValidationError: 1 validation error for Delivery
dimensions.0
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='あああ', input_type=str]
m2 = Delivery(timestamp='2024年01月12日 21時45分00秒', dimensions=['10.0', '20.0'])
ValidationError: 1 validation error for Delivery
timestamp
Input should be a valid datetime, invalid date separator, expected `-` [type=datetime_parsing, input_value='2024年01月12日 21時45分00秒', input_type=str]
Example
Userクラスを定義。
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
外部データをUserクラスの定義に適合させる。
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
b'cheese': 7,
'cabbage': '1',
},
}
external_data = {
'id': 123,
'signup_ts': '2019-06-01 12:22',
'tastes': {
'wine': 9,
b'cheese': 7,
'cabbage': '1',
},
}
user = User(**external_data)
print(user.id)
print(user.model_dump())
適合できるものは適合してくれている。
123
{'id': 123, 'name': 'John Doe', 'signup_ts': datetime.datetime(2019, 6, 1, 12, 22), 'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1}}
適合できない=バリデーションに失敗したらValidationErrorが例外で上がって、どこが悪いのかを教えてくれる。
from datetime import datetime
from pydantic import BaseModel, PositiveInt
from pydantic import ValidationError
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
external_data = {'id': 'not a int', 'tastes': {'a': -1}}
try:
User(**external_data)
except ValidationError as e:
print(e.errors())
[
{
'type': 'int_parsing',
'loc': ('id',),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'input': 'not a int',
'url': 'https://errors.pydantic.dev/2.5/v/int_parsing'
},
{
'type': 'missing',
'loc': ('signup_ts',),
'msg': 'Field required',
'input': {
'id': 'not a int',
'tastes': {'a': -1}
},
'url': 'https://errors.pydantic.dev/2.5/v/missing'
},
{
'type': 'greater_than',
'loc': ('tastes', 'a'),
'msg': 'Input should be greater than 0',
'input': -1,
'ctx': {'gt': 0},
'url': 'https://errors.pydantic.dev/2.5/v/greater_than'
}
]