Web APIとパラメータ (P)
はじめに
Web API へのアクセスも、基本 URL を作ってアクセスし、出力結果を Stream から取り出すことはできます。 ただ、以外と直接使うのは難しい・面倒なことも多いですよね。
- Web API の引数の処理をしなくては
- API 引数の文字列に、空白とか、日本語とか入れるときの変換 (URL encoding) を自分でやるの?
- PUT, POST などの際は、どうやってデータを送るんだろう?
- 取得したデータは bytes だけど、文字列に変換してから JSON なら dict に変換するのか?
ってことで、今回はライブラリをつかって楽しましょう。 Java ではRetrofit が有名なんですが、Python にも Retrofit に影響をうけたUplinkがあるので、使ってみます。
Uplink の詳しい使い方に興味がある人は、Uplink quickstartを読むとよいでしょう。
利用プログラム例
利用する Web API
皆さんがプログラミングに慣れるまでは、一般の Web API にアクセスするのではなく、 鎌田の方で準備した Web API もどきにアクセスしてもらいましょう。
このページでは、Firebase 上に鎌田が準備したデータに対して、Web API を用いてアクセスしてもらいます。 準備したのは、カレンダー共有アプリを真似したものです。 Web API は以下の二つ:
- ユーザ一覧情報
- URL: https://api-demo-35b66-default-rtdb.asia-southeast1.firebasedatabase.app/public.json
- 上記 URL にアクセスすると、以下のような JSON データが取得できます。
- key が
user_id
で、value の object に、name
やarea
で利用者の名前や地域の情報がでていると- url encoding のテストのため、あえて日本語の
user_id
を混ぜてみました
- url encoding のテストのため、あえて日本語の
{ "userA":{
"area":"神戸",
"name":"コウナン太郎"
},
"userB":{
"age":23,
"area":"神戸",
"favorite":"りんご",
"name":"岡本次郎"
},
"祝日さん":{
"area":"日本",
"name":"祝日カレンダー"
}
}
- 各ユーザのカレンダー情報:
{"2024-10-14":"スポーツの日","2024-11-03":"文化の日","2025-01-01":"元日"}
完成したら、ユーザは自分の興味のあるカレンダーを複数入手して表示する感じってことで。
Web API の利用イメージ
上記の機能を、こんな感じでサクッと使えたらいいですよね。
dct = service.get_users() # ユーザ一覧情報取得
print(dct)
events = service.get_events('userA') # 'userA' のイベント取得
print(events)
holidays = service.get_events('祝日さん') # '祝日さん' のイベント取得
print(holidays)
そのためには、例えば service.get_events('userA')
の場合
- サイト
https://api-demo-35b66-default-rtdb.asia-southeast1.firebasedatabase.app/
以下の events/{user_id}.json
にアクセスしないといけない。user_id
には'userA'
,'祝日さん'
などの str が来るので、当然 url encoding が必要になる- url encoding の説明はうしろで。
- 返り値は JSON の形で返される
なんてことをライブラリに教えてあげないといけません
Web API の呼出し方を指定
先程の指定ですが、これぐらいでかけちゃうのが、ライブラリのすごい所です。 コードは、json 版: request_sample.pyです。
from uplink import Consumer, get, returns
class CalendarService(Consumer): # カレンダー共有サービスの定義
"""A Python Client for the GitHub API."""
@returns.json() # 「結果 JSON だから dict にしてね」の指定
@get('public.json') # path の指定
def get_users(self): # method 宣言 (self は service.get_users() の service 相当
"""Get a user list.""" # 定義部はライブラリが自動対応
@returns.json()
@get('events/{user_id}.json') # path の指定、引数 user_id が {user_id} に埋め込まれる
def get_events(self, user_id): # method 宣言 (service.get_events(user_id) って感じで利用)
"""Get an event list of the given user.""" # 定義部はライブラリが自動対応
service = CalendarService(base_url='https://api-demo-35b66-default-rtdb.asia-southeast1.firebasedatabase.app/')
@...
というのは python ではデコレータと呼ばれるもので、これでライブラリに Web API にアクセスするために必要な情報を渡して、
メソッドの中身を埋めてくれるんです。
python クラスへの変換
dict じゃなくてクラスにしてくれって人へ。
上記で dict を取得できたので、class にしたい人はpydantic をつかって dict から python クラスのオブジェクトに再変換すればOKです。が、もっと Web API を関数っぽく書くこともできます。 pydantic 版: request_pydantic.pyのコードになります。興味がある人はどうぞ。
変わったのは、get_user(self)
メソッドの後ろに -> Dict[str, User]
と型情報が加わったところです。
@returns.json() # 「結果 JSON だから dict にしてね」の指定
@get('public.json') # path の指定
def get_users(self) -> Dict[str, User]: # method 宣言、返り値型は dict (key: str, value: User の dict)
"""Get a user list.""" # 定義部はライブラリが自動対応
あとは、User
クラス定義と、pydantic に変換させるためにも型情報がついていますね。
非同期処理は?
uplink では、ネットワーク呼出しは同期呼出しといって、結果が返ってくるまで待つスタイルになっていました。 ただ、ネットワーク処理はまたさせることもあるし、失敗することもあるので、 処理待ちをしているとプログラムが固まったみたいになって嬉しくないことも多いんです。
非同期処理については、後日紹介予定です。
補足:URL encoding
Web API の GET メソッドを呼び出すときは、URL の形で引数が渡されます。 ただ、URL 中には日本語とか書けませんし、space などもかけません。
こういう場合は、引数を URL encoding という形式に変換して送ることになっています。 皆さんも、Web browser の URL 欄には、
https://www.google.com/search?q=甲南大学
とか書いてあるのに、copy & paste すると
https://www.google.com/search?q=%E7%94%B2%E5%8D%97%E5%A4%A7%E5%AD%A6
って変わる経験があるんじゃないでしょうか。
URL encoding を直接おこなうには、python では urllib.parse などが利用できますが、uplink も対応してくれています。