きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Pydanticで型定義を流用したら正常動作しなくなった(シリアライズ)

始めに

結果が分かれば単純なミスでした。Pydanticで型定義しただけと考えていましたが、少しでも振る舞いが変わるときはちゃんと確認しましょう。

環境

  • Python
    • 3.13
  • Pydantic
    • 2.11.4

実装

変更前のコード

SaaS企業が外部連携しやすいようにライブラリを公開していることがあります。基本的にはライブラリ内で型定義はすでに行われている状態ですが、SaaS側でユーザが自由にプロパティを追加・修正できる項目がある場合には、該当プロパティを参照及び更新するためには自前でカスタマイズして連携する必要があります。今回発生したケースでもその自前カスタマイズをする必要があったのですが、コード上で特に型定義もせずに直接dictで設定していました。

from typing import Any
from pydantic import BaseModel

class Update(BaseModel):
  id: int
  data: Any 

record = Update(
  id=1,
  data={
    "id": "aaa",
    "value": "ccc",
    "pattern": 1,
    "now": "2025-05-16T17:32:30.204923+09:00"
  }
)

# record インスタンスをもとに 外部ライブラリに連携する処理

変更後のコード

型がないと横展開時の漏れに気づけなかったり、必要なパラメータが足りているのか判断が難しくなるため、今後の拡張性を見越して型定義をしました。リファクタリングとしてpydanticで型定義しつつ、 model_dumpを使用することでdictで渡せるように変更しました。

from typing import Any
from datetime import datetime
from enum import IntEnum
from pydantic import BaseModel

class PatternEnum(IntEnum):
    A = 0
    B = 1    
    
class CustomModel(BaseModel):
    id: str
    value: str
    pattern: PatternEnum
    now: datetime

class Update(BaseModel):
  id: int
  data: CustomModel

record = Update(
  id=1,
  data=CustomModel(
    id="aaa",
    value="ccc",
    pattern=1,
    now=datetime.now()
  ).model_dump()
)

# record インスタンスをもとに 外部ライブラリに連携する処理

ただし、これではうまく動きませんでした。原因としては、SaaSと連携するために最終的にシリアライズしてデータ連携する必要があるのですが、datetimeをシリアライズしたタイミングで次のエラーが出るからです。

*** TypeError: Object of type datetime is not JSON serializable

対策としてmode='json'を付与することで、シリアライズも行えるようになっています。

{'id': 'aaa', 'value': 'ccc', 'pattern': 1, 'now': '2025-05-16T17:44:50.670465+09:00'}

また、ライブラリ側の型ヒントがdictではなく、strでも問題ない場合はmodel_dump_jsonを使用するといいでしょう。

'{"id":"aaa","value":"ccc","pattern":1,"now":"2025-05-16T17:44:50.670465+09:00"}'

ソースコード

実際のテストコードで問題と解決策を確認できます: - GitHub: test_to_json.py - Pydanticモデルのシリアライズテスト

まとめ

外部ライブラリに連携する際には、model_dumpmode=jsonを使用すると安心。 または、文字列でよければmodel_dump_jsonを使用する。

終わりに

リファクタリングと言いつつ、strdatetimeを型変換している箇所が悪いんですけどね。元々、一旦DBに格納した情報をSaaS側に反映する用途で使用していたため、DBで使用していた型をそのまま流用してしまっていました。流用すること自体は問題ないのですが、差分を理解しないまま流用してはいけませんね。

特に、Pydanticで型定義を導入することは開発効率とコードの品質向上に大きく貢献しますが、型変換の過程でデータがどのように変化するかを理解しておくことが重要です。今回はシンプルなケースでしたが、これを機にPydanticのmodel_dumpmodel_dump_jsonの挙動についても理解を深めることができました。単純ですが勉強になりました。