Requests や HTTPX などのライブラリを利用して HTTP リクエストを送り、JSON レスポンスをアプリケーション内で参照するといったケースはよくあると思います。
例えば HTTPX を利用して愚直に実装すると以下のように記述できます。
import httpx r = httpx.get("https://jsonplaceholder.typicode.com/users/1") r_json = r.json() print(r_json.get("name")) # => Leanne Graham # OR print(r_json["name"])
しかしネストした複雑な JSON を扱う場合は更に記述量が増えますし、エディタによる型補完もありません。何重にもネストしたフィールドの値を取り出す場合はさらに .get
が増えて辛いですね。
import httpx r = httpx.get("https://jsonplaceholder.typicode.com/users/1") r_json = r.json() print(r_json.get("company").get("name")) # => Leanne Graham # OR print(r_json["company"]["name"])
今回は Pydantic を利用して JSON レスポンスに型情報を付与してあげて、エディタの型補完が可能となるようにしてみます。
サンプルとして JSONPlaceholder の /users
リソースを利用します。
{ "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }
前提
クラスの定義
まずは下準備として BaseModel を継承したクラスを作成してあげます。
from pydantic import BaseModel class Geo(BaseModel): lat: str lng: str class Address(BaseModel): street: str suite: str city: str zipcode: str geo: Geo class Company(BaseModel): name: str catchPhrase: str bs: str class User(BaseModel): id: int name: str username: str email: str address: Address phone: str website: str company: Company
User インスタンスの生成・インスタンスの利用
/users/1
にリクエストすると、r_json
の型は dict になります。そのため r_json
の値をアンパックして User インスタンスを生成します。これで user
の型は User
となります。JSON 内の company
フィールドの name
フィールドを参照したい場合は、user.company.name
でアクセスすることが可能で、かつエディタの型補完も効きます。
r = httpx.get("https://jsonplaceholder.typicode.com/users/1") r_json = r.json() user = User(**r_json) print(user.company.name) # => Romaguera-Crona
注意点
API のスキーマが変わってしまうと Pydantic で ValidationError が発生してしまうため注意が必要です。API のスキーマ変更とあわせてクラスの変更も忘れないようにしましょう。以下はリクエスト URL を /user/1
から /posts/1
に変更した場合のエラー内容です。
Traceback (most recent call last): File "/Users/enokawa/workspace/github.com/enokawa/python-sandbox/httpx-sample/httpx_sample/main.py", line 43, in <module> main() File "/Users/enokawa/workspace/github.com/enokawa/python-sandbox/httpx-sample/httpx_sample/main.py", line 38, in main user = User(**r_json) ^^^^^^^^^^^^^^ File "/Users/enokawa/.anyenv/envs/pyenv/versions/3.11.3/lib/python3.11/site-packages/pydantic/main.py", line 171, in __init__ self.__pydantic_validator__.validate_python(data, self_instance=self) pydantic_core._pydantic_core.ValidationError: 7 validation errors for User
おわりに
Pydantic で JSON レスポンスに型情報を付与する方法を紹介しました。その他の利用例として、boto3 で Secrets Manager の get_secret_value
のレスポンスにも型情報を付与するといったことも可能になります。僕がいま関わっているプロジェクトでも多段 .get
を利用している部分が多くあるため、少しずつこの方式に変えていきたいと企んでいます。
サンプルコード全文は以下を参照してください。