Python 3.11.2
Pydantic 2.3.0
FastAPI 0.103.0
SQLAlchemy 2.0.20
uvicorn 0.23.2
BtoC向けサービスのコードを書いてテストを実施し、デプロイしたら予期しないエラーが発生した、ということはエンジニアの誰しもが経験したことがあるでしょう。
テストがすべての事故を防いでくれるわけではありません。エンドユーザーは開発者が予想もしない入力をすることがあり、通信されるデータの形式のすべてを把握することは非常に難しいです。
こういったバグを出してしまい、プロダクトの安全性を担保できないことはエンジニアにとって許容し難いものです。
Pydanticを使えば、この問題に対処する堅牢性アップへ貢献できます。
HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。
✓ 質問し放題
✓ β版公開中(2025年内の特別割引)
2023年8月現在、世界で最も利用されているPythonのデータバリデーションライブラリがPydanticです。
現在はバージョン2.3.0になり、アップデートが早いためライブラリの有用性も短期サイクルで向上しています。
多くのlinterやIDEにも対応しているため、Pythonエンジニアは採用するべきライブラリです。
dataclassやTypeDictといった組み込みライブラリとの親和性ドキュメントを読むより触ったほうが早いので触ってみましょう。
pipでインストールできます。
サンプルとして、注文情報のモデルを作成してみます。
モデルは、エンティティ(データ)に対する種類や内容を定義すること、と覚えておけばOKです。
これを実行した結果は以下です。
これは成功例ですが、このコードのid部分を変更して文字列にして再実行みましょう。
これを実行すると以下のようなエラーが出ます。
このように、定義された型に対して異なる型が入力されるとバリデーションエラーを出し、データ保全を助けてくれるのがPydanticです。
Pydanticを利用する取っ掛かりとして最適なFastAPIアプリケーションの実装例を見ていきましょう。
FastAPIはPythonのミニマルなRESTful APIアプリケーションフレームワークです。しばしばフロントエンドフレームワークと組み合わせて使われます。
今回は例として、商品テーブルを参照するRESTful APIを作る前提で、Product(商品)モデルを使うアプリケーションを作っていきます。
準備として、fastapi、uvicorn、pydantic、sqlalchemyをインストールしましょう。
最終的なディレクトリ構成は以下となります。
models/ディレクトリ配下にbase.pyを作りましょう。
このbase.pyはベースモデルを定義します。今回はただsqlalchemyのベース宣言クラスのDeclarativeBaseを継承するだけです。通常は、ここでidやcreated_atなどの共通カラムの定義やデータベース接続を記載します。
次にmodels/product.pyを見てみましょう。
ここではproductsテーブルに「ID」「商品名」「価格」「商品説明」を定義しています。先ほど作ったbase.pyのBaseを継承しています。PydanticにBaseModelが用意されていますが、sqlalchemyと組み合わせるためここでは使用しません。
モデルの定義とは別に、HTTPリクエストされた際に「どのような値を受け取るか」「どのような値を返すか」を定義します。
同じファイル名ですが、次はschemas/ディレクトリ以下のschemas/product.pyを以下のようにします。
ProductBaseで共通となるフィールドを定義しています。
schemasをロードしたら使えるようにするため、__init__.pyも以下のようにします。
上記で定義した内容を受け取るアプリケーションを作りましょう。
services/product.pyを以下のようにします。
FastAPIのルーティング機能を使います。これを利用するためにapp.pyを以下のように書きます。
このようにすることで[GET]/product/{id}と[POST]/productにルーターを設定できます。
以下のコマンドでアプリケーションを立ち上げましょう。
http://localhost/product/1にブラウザでアクセスすると、以下のようなJSONが返ってきます。
さらにcurlコマンドを使ってcreateのAPIにもアクセスしてみましょう。
これで以下のようなレスポンスが返ってきたら完了です。
エラーケースも試しておきましょう。本来であればcreateのAPIにはnameフィールドは必須ですが、これを省いてリクエストしてみます。
このリクエストに対する結果は以下です。
正しくエラーとして出力されています。
実装例を紹介すると長くなりすぎるため、ここでは省略しますが、Pydanticには以下のような機能があります。
computed_fieldは、@propertyや@cached_propertyデコレーターに関数の返り値を付与できます。
以下は商品の単体重量に対して数量をかけた総重量を付与した例です。
個人的に便利だなぁと感じるバリデーションデコレーターである@validate_callの紹介です。
関数の呼び出しの前に引数チェックが行なえます。
これの出力結果は以下です。
実際に関数の呼び出しが行われることはないため、より安全な実装に役立ちます。
Pydanticはデフォルトでlax mode(緩めモード)という状態です。言葉が意味する通り、整数値を文字列として渡しても自動で解釈してくれます。
これは助かるケースもありますが、より堅牢な実装をしたいときは以下のようにstrict modeで実装するべきです。
上記の出力結果は以下です。
Pydanticの設定ファイルの拡張にpydantic-settingsがあり、便利な設定値として定義できます。
設定値のプリフィックスや、dotenvのサポート、そしてDockerのSecrets対応など、本番環境で使いやすい機能が揃っています。
pipでインストールしましょう。
自身で設定したい項目をまとめて設定可能なので、保守性にも優れています。
この出力結果は以下です。
エラーハンドリングは基本的にValidationErrorに対するraiseで事足ります。
キャッチした例外がとてもわかりやすいので、エラー発生箇所が特定しやすくなっています。
今回はPydanticの基本機能を紹介しましたが、OpenAPIでAPI仕様を確認したり、単体テストを書いたりなど、本番では考慮する点がまだまだあります。
堅牢性の高いアプリケーションの構築は、すべてのPythonアプリケーションに対して毎回存在する課題です。
エンジニアとして、質の高いコーディングをしたいのはもちろんですが、Pydanticのようなライブラリを効率的に利用することで省コード化していきましょう。意図しない入力を防ぐことができれば、それだけで大幅に事故防止へつながります。