Pydanticによるデータ検証

データ検証パッケージ Pydantic を紹介する.

Pydanticとは

Pydantic はデータ検証のためのパッケージである.

Pythonは,動的に型付けを行うのが特徴であり,それは利点でもあり弱点でもある.たとえば,以下のようなコードが書ける.

x = 1 #整数型 int に型付け
type(x)
int
x = "Hello"
type(x)
str

型(タイプ)を気にする必要がないので,初学者が気楽にプログラムを書けるというは利点であるが, ちゃんとしたプログラムを書きたい人にとっては,この仕様は嬉しくない.

そのため,最近のPythonでは型ヒントを与えることができるようになった.たとえば,以下のように整数を2倍した整数を返す関数を定義できる.

def multiple2( x:int ) -> int:
    return x*2
multiple2(100)
200

これは,引数のxを整数型 int で,返値も整数型であるように型ヒントを与えたものであるが, これは単にコードを読みやすくするためのヒント(飾り)であるため,実際には文字列を引数として与えてもエラーしない.

multiple2("Hello")
'HelloHello'

こういった予期しない結果を出さないようにするための手段がデータ検証 (data validiation) である. Pydanticを使うと,データ検証が容易になるだけでなく,クラスを設計するのが楽になる.

早速使ってみよう.まずは,PydanticのBaseModelクラスから派生させてUserクラスを作ってみる. このクラスは,整数値をとるインデックス id と文字列の名前 name の2つの属性(フィールド)をもつ.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str = "Mikio Kubo"

idは必須であり省略できない.一方,nameは既定値が指定されているので省略できる. 以下では,文字列 ‘123’ でidを指定しているが,型指定機能で自動的に整数値に変換される. また,nameは省略すると既定値が代入される.

user = User(id='123')
user
User(id=123, name='Mikio Kubo')

問題 1

  1. idなしでUserクラスを使うとどうなるか?
  2. name引数に自分の名前を入れるとどうなるか?

Pydanticで作ったクラスのインスタンスは,辞書に変換できる. 変換にはmodel_dumpメソッドを用いる.辞書のキーはフィールド名になる.

dumped_user = user.model_dump()
print(type(dumped_user))
dumped_user
<class 'dict'>
{'id': 123, 'name': 'Mikio Kubo'}

データのWeb経由での交換の際には,JSON (JavaScript Object Notation) 形式のテキストファイルが便利である. 変換にはmodel_dump_jsonメソッドを用いる.

json_user = user.model_dump_json()
print(type(json_user))
json_user
<class 'str'>
'{"id":123,"name":"Mikio Kubo"}'

クラスから作られた辞書やJSONから,クラスを再現することもできる. 辞書からはmodel_validateメソッド,JSONからはmodel_validate_jsonメソッドを用いる.

print( user.model_validate(dumped_user) )
print( user.model_validate_json(json_user) )
id=123 name='Mikio Kubo'
id=123 name='Mikio Kubo'

問題 2

  1. 自分の名前をnameに設定したUserインスタンスを作り,それを辞書に変換せよ.
  2. 今度はJSON形式のテキストに変換せよ.
  3. 変換した辞書とJSONから,元のクラスインスタンスを生成せよ.

Fieldクラスでフィールドの値の既定値や範囲の指定など様々な情報を付加することができる. 以下の例では,idはge(greater than equal)を用いて1以上の値に制限し, 名前の既定値 (default value) はNoneとしている.

from pydantic import Field

class User(BaseModel):
    id: int   = Field(ge=1)
    name: str = Field(default=None)

user = User(id=1)
print(user)
id=1 name=None

上のクラスのidに0を入れるとエラーする.この検証エラーをtry…except構文でとらえて,エラーを表示するには, ValidationErrorを用いる.

from pydantic import ValidationError

try:
    user = User(id=0)
except ValueError as e:
    print(e)
1 validation error for User
id
  Input should be greater than or equal to 1 [type=greater_than_equal, input_value=0, input_type=int]
    For further information visit https://errors.pydantic.dev/2.1.2/v/greater_than_equal

問題 3

  1. nameフィールドの既定値を自分の名前にし,0以上,120以下の値をとるフィールド age を追加したクラス Userを作れ.
  2. 上で作ったクラスに age = 150 を入れるとエラーする.エラーメッセージを出すようなコードに直せ.

標準のint, str, float, boolの型だけでなく,様々な型を指定することができる. 型クラスは,typingパッケージからインポートしておく.

from typing import Tuple, List, Dict, Set, Union, Optional

class User(BaseModel):
    id: int
    height: Union[int, float]         # 整数か浮動小数点数のいずれか(型の和集合)
    name: Optional[str]       = None  # 省略可能 (ただし既定値は必要)
    friends: List[str]                # 友人の名前を入れ文字列のリスト
    fruits: Dict[str,int]             # 好きなフルーツ名をキー,購入数を値とした辞書

User(id = 123, 
     height = 178.0, 
     friends = ["Kitty", "Mickey", "Donald"],
     fruits = {"apple":10, "melon":3}
    )
User(id=123, height=178.0, name=None, friends=['Kitty', 'Mickey', 'Donald'], fruits={'apple': 10, 'melon': 3})

問題 4

以下のフィールドをもつクラスCustomerをPydanticのBaseModelから派生させて作れ. また,適当なデータを用いて

  1. 整数か文字列のid
  2. 文字列のname
  3. 緯度・経度を表す浮動小数点数のタプルのlocation
  4. 扱う商品の名前を文字列とした集合のproducts