Upgrade to Pro — share decks privately, control downloads, hide ads and more …

モデルの定義に基づくバリデーションを実現するためのpydantic入門

 モデルの定義に基づくバリデーションを実現するためのpydantic入門

Daiki Katsuragawa

July 12, 2022
Tweet

More Decks by Daiki Katsuragawa

Other Decks in Programming

Transcript

  1. モデルの定義に基づくバリデーションを 実現するためのpydantic入門 桂川大輝

  2. 複数の部品によって構成されるシステムを開発していますか? • 多くのシステムは複数の部品によって構成 ◦ 部品:モジュール・クラス・メソッドなど • 各部品が役割と責任を持ち連携 ◦ 各部品の入出力を把握しておけばシステムの全ての把握は不要 ◦

    入出力:int、str、JSON/辞書(構造) 2 部品A 部品B int str 部品Y 部品Z JSON/辞書 JSON/辞書
  3. 課題①:入出力で扱うデータの定義がわからない(可読性の課題) • 他の部品の出力を入力として受け取る時、 他の部品の実装を深く見る必要がある場面も… (例:JSON/辞書といった型まではわかるが構造はわからない) 3 部品Y 部品Z JSON ???のデータを

    格納したJSONを 渡します。 何が格納されている? キーは?
  4. 課題②:入出力で扱うデータが正しくない(堅牢性の課題) • 他の部品の出力を把握しているものの 期待と異なる、正しくないデータを受け取る場面も… (例:年齢が「-1」、型が異なる、約束しているデータがない) 4 部品Y 部品Z JSON ◯△□のデータを

    格納したJSONを 渡します。 ドメイン的に ありえないデータが来た
  5. 課題を解決する“pydantic” • “pydantic”とは ◦ モデルを定義することでデータのバリデーションを実現するライブラリ ◦ 辞書型・JSONとの変換も可能であり部品の入出力に有用 • 課題①:入出力で扱うデータの定義がわからない(可読性の課題) ◦

    モデルの定義により把握が可能に(規約化) • 課題②:入出力で扱うデータが正しくない(堅牢性の課題) ◦ 定義に基づくバリデーションにより正しいデータのみが存在する状態に 5 !pip install pydantic
  6. pydanticの利用例〜Userクラスの定義①〜 6 from pydantic import BaseModel, Field class User(BaseModel): id:

    int name: str age: int = Field(ge=20) external_data = { 'id': 1, 'name': 'パイソン 太郎', 'age': 20 } user = User.parse_obj(external_data) 型、有効範囲の定義 定義を満たす インスタンスの生成 Userクラスの定義の 読み取りが可能 (例:ageは20以上)
  7. pydanticの利用例〜Userクラスの定義②〜 7 from pydantic import BaseModel, Field class User(BaseModel): id:

    int name: str age: int = Field(ge=20) external_data = { 'id': 1, 'name': 'パイソン 太郎', 'age': 19 } user = User.parse_obj(external_data) 有効範囲外の値 ValidationError
  8. pydanticの利用例〜ValidationErrorの中身〜 8 (省略) ValidationError: 1 validation error for User age

    ensure this value is greater than or equal to 20 (type=value_error.number.not_ge; limit_value=20) エラーの詳細を確認可能 (例:ageの20以上という定義を満たしていない)
  9. pydanticの利用例〜Userクラスを引数とする関数①〜 9 from pydantic import validate_arguments @validate_arguments def input_user(user :

    User) -> None: pass external_data = { 'id': 1, 'name': 'パイソン 太郎', 'age': 20 } input_user(external_data) 引数のバリデーションを 実行するデコレーター pydanticで定義した クラスも引数として指定
  10. pydanticの利用例〜Userクラスを引数とする関数②〜 10 from pydantic import validate_arguments @validate_arguments def input_user(user :

    User) -> None: pass external_data = { 'id': 1, 'name': 'パイソン 太郎', 'age': 19 } input_user(external_data) 有効範囲外の値 ValidationError
  11. pydanticの利用例〜Userクラスを返り値(辞書型)とする関数①〜 11 def output_user() -> dict: external_data = { 'id':

    1, 'name': 'パイソン 太郎', 'age': 20 } user = User.parse_obj(external_data) return user.dict() output_user() やりとりしやすい形式(辞書型)で出力 {'age': 20, 'id': 1, 'name': 'パイソン 太郎'}
  12. pydanticの利用例〜Userクラスを返り値(辞書型)とする関数②〜 12 def output_user() -> dict: external_data = { 'id':

    1, 'name': 'パイソン 太郎', 'age': 19 } user = User.parse_obj(external_data) return user.dict() output_user() 有効範囲外の値 ValidationError
  13. pydanticの利用例〜デコレーターによる詳細なバリデーションの実現〜 13 from pydantic import BaseModel, Field, validator import unicodedata

    class User(BaseModel): id: int name: str # 半角空白を含む(※前後以外) age: int = Field(ge=20) @validator("name") def check_contain_space(cls, v): if " " not in v.strip(): # 前後の半角空白を無視 raise ValueError("ensure this value contains spaces") return v.strip() # 前後の半角空白を削除 デコレーターによる詳細な バリデーションの実現も可能 (例:半角空白を含むかを判定)
  14. まとめ • 複数の部品によって構成されるシステムの開発 ◦ 部品:モジュール・クラス・メソッドなど ◦ 各部品が役割と責任を持ち連携 ◦ 課題①:入出力で扱うデータの定義がわからない(可読性の課題) ◦

    課題②:入出力で扱うデータが正しくない(堅牢性の課題) • 課題を解決する“pydantic” ◦ モデルを定義することでデータのバリデーションを実現するライブラリ ◦ 課題①:モデルの定義により把握が可能に(規約化) ◦ 課題②:定義に基づくバリデーションにより正しいデータのみが存在する状態に 14
  15. 15 データフレームのバリデーションを実現するためのpandera入門 - Speaker Deck (https://speakerdeck.com/daikikatsuragawa/detahuremufalsebaridesiyonwoshi-xian-surutamefalsepanderaru-men) 付録

  16. 16 データフレームのバリデーションを実現するためのpandera入門 - Speaker Deck (https://speakerdeck.com/daikikatsuragawa/detahuremufalsebaridesiyonwoshi-xian-surutamefalsepanderaru-men) 付録

  17. pydanticとpanderaの相性が良い2つのポイント • 類似した記述方法 • 組み合わせた利用 17

  18. pydanticとpanderaの類似した記述方法 18 # pydantic # Userモデル from pydantic import BaseModel,

    Field class User(BaseModel): name: str age: int = Field(ge=20) # pandera # Userをレコードとするデータフレームのスキーマ import pandera as pa from pandera.typing import Series class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20)
  19. pydanticとpanderaの類似した記述方法(差分) 19 # pydantic # Userモデル from pydantic import BaseModel,

    Field class User(BaseModel): name: str age: int = Field(ge=20) # pandera # Userをレコードとするデータフレームのスキーマ import pandera as pa from pandera.typing import Series class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20)
  20. pydanticとpanderaを組み合わせた利用① 20 import pandera as pa from pandera.typing import Series,

    DataFrame class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20) class UserDataFrameModel(BaseModel): name: str user_df: DataFrame[UserSchema] データフレームのスキーマ(panderaにより定義)を 要素にもつモデル(pydanticにより定義)
  21. pydanticとpanderaを組み合わせた利用② 21 import pandera as pa from pandera.engines.pandas_engine import PydanticModel

    # pandera 0.10.0~ from pandera.typing import Series from pydantic import BaseModel, Field class User(BaseModel): name: str age: int = Field(ge=20) class UserSchema(pa.SchemaModel): class Config: dtype = PydanticModel(User) coerce = True モデル(pydanticにより定義)に従う レコードの集合となるデータフレームの スキーマ(panderaにより定義)
  22. まとめ〜pydanticとpanderaの相性が良い2つのポイント〜 • 類似した記述方法 ◦ 記述方法は対応しており差分は少ない ◦ 一方に慣れていれば他方の導入も容易 • 組み合わせた利用 ◦

    データフレームのスキーマ(panderaにより定義)を要素にもつモデル (pydanticにより定義) ◦ モデル(pydanticにより定義)に従うレコードの集合となる データフレームのスキーマ(panderaにより定義) 22