Python 3.11.2
Pydantic 2.3.0
FastAPI 0.103.0
SQLAlchemy 2.0.20
uvicorn 0.23.2
BtoC向けサービスのコードを書いてテストを実施し、デプロイしたら予期しないエラーが発生した、ということはエンジニアの誰しもが経験したことがあるでしょう。
テストがすべての事故を防いでくれるわけではありません。エンドユーザーは開発者が予想もしない入力をすることがあり、通信されるデータの形式のすべてを把握することは非常に難しいです。
こういったバグを出してしまい、プロダクトの安全性を担保できないことはエンジニアにとって許容し難いものです。
Pydanticを使えば、この問題に対処する堅牢性アップへ貢献できます。
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のようなライブラリを効率的に利用することで省コード化していきましょう。意図しない入力を防ぐことができれば、それだけで大幅に事故防止へつながります。