snaqme Engineers Blog

おやつ体験メーカー snaq.me エンジニアによる開発プロダクトブログ

FastAPI 0.100 (Pydantic v2)にアップグレードしました

こんにちは スナックミー CTO の三好 (@miyoshihayato) です

はじめに

スナックミーはマイページのAPI開発をFastAPIで開発し、フロントはReactで開発しています。

詳しくは FastAPIについての採用背景

labs.snaq.me

フロントの取り組み

labs.snaq.me

こちらをご覧ください。

背景・課題

以前は、品質とスピードの間にトレードオフがあるという思い込みにより、プロダクト開発を進めていました。その結果、アップグレードへの対応力が低く、アップグレード作業には多大な労力が必要でした。

上記込みで様々な課題を解決するために 2021年にPHPからFastAPIのリプレイスすると決めて動き始めました。 課題は先ほどのFastAPIの採用背景をご覧ください。要約すると以下が要因になります。

* 高学習コスト
* コードの質
* 心理的安全性

今年7月、FastAPIのアップグレードが発表され、これを機に変更容易性を意識したアップグレードに取り組みました。(今は 2023/12/7 現在 0.104.1 が動いてます)

FastAPIの対応

主に対応したところのリスト 主に Pydantic V2に関するアップグレードがメイン

  • dict() から model_dump() への移行
  • __root__ から RootModel への移行
  • __fields__ から model_fields への移行
  • class Config から model_config = ConfigDict() への移行
  • @validation から @field_validator@model_validator への移行
  • Optional の挙動の整理
  • Fieldexampleexamples への変更
  • deprecatedjson_schema_extra への変更

非推奨から推奨方法へ

  • dict() から model_dump()
  • __root__ から RootModel
  • __fields__ から model_fields
  • class Config から model_config = ConfigDict()

必須対応

  • Optional の挙動の整理
  • Fieldexampleexamples への変更
  • deprecatedjson_schema_extra への変更

Optional 挙動の整理

v1

class UserName(BaseModel):
    name: Optional[str] = Field(title="ユーザー名", example="須那田 クミ子")

以前は、nameがNoneの場合、Noneとして扱われましたが、v2ではエラーになるため、以下のようにFieldにNoneを宣言する必要があります。

v2

class UserName(BaseModel):
    name: Optional[str] = Field(None, title="ユーザー名", example="須那田 クミ子")

Fieldの仕様変更

  • example から examples
  • deprecated から json_schema_extra

v1

class UserName(BaseModel):
    name: Optional[str] = Field(
        None,
        title="ユーザー名",
        example="須那田 クミ子",
        deprecated=True
    )

v2では、exampleexamples に変更し、deprecatedjson_schema_extra で表現します。

v2

class UserName(BaseModel):
    name: Optional[str] = Field(
        None,
        title="ユーザー名",
        examples=["須那田 クミ子"],
        json_schema_extra={"deprecated": True}
    )

Python 3.11の対応

Python 3.11へのアップデートは、Pythonのバージョンを3.8から3.11にすることによるもので、主に以下の点に対応しました。

  • Dict, List, Optional, Tuple, Union の基本廃止
  • インポート量の削減
  • コードのシンプル化

3.8

class SnaqmeInfo:
    def __init__(self) -> None:
        self.__name = str
        self.__items: List[Dict[str, int]] = []
        self.__status: Optional[Union[int, List[int]]] = None

3.11

class SnaqmeInfo:
    def __init__(self) -> None:
        self.__name = str
        self.__items: list[dict[str, int]] = []
        self.__status: int | list[int] | None = None

まとめ

今回のアップグレードは、時間的にも問題的にもスムーズに本番環境へデプロイできました。時間は合計で10時間ほどで20時間はかかってないと思います。 その成功要因としては、

  • テストのカバレッジが高い(95%以上)
  • ステージング環境での事前テスト
  • 異常検知システムの有効活用

などが挙げられます。新しい取り組みではなく、基本をしっかり押さえることがスケールアップにおいて重要であると改めて実感しました。弊社は全てを完璧に押さえることは難しいかもしれませんが、常に意識して取り組むことで、現状と将来への最善のバランスを図りながら、チームとしての開発を進めていきたいと考えています。

また、Pydantic V2へのアップグレードにより、型の取り扱いがより厳密になったと感じています。これにより、以前は曖昧だった部分をより明示的に表現しやすくなったという印象を持っています。

engineers.snaq.me

募集ポジション

ソフトウェアエンジニア Backend (lead) / 株式会社スナックミー

ソフトウェアエンジニア FrontEnd (lead) / 株式会社スナックミー

ソフトウェアエンジニア Data Engineer / 株式会社スナックミー