2018年6月14日木曜日

swagger-codegen を使って Python のコードを生成してみよう

概要

前回 swagger-codegen を使って Ruby のコードを生成してみました
今回は Python のコードを生成しました
しかも今回は生成されたコードを修正して動くところまで実装してみます
Python2 で動作するコードも生成できますが今回は Python3 で動作するコードを生成します

環境

  • macOS 10.13.5
  • docker 18.03.1-ce
  • Python 3.6.5

サーバコードの生成

前回同様、使用する swagger.json は PetShop の JSON を使います
Python の場合 flask ベースのサーバコードを生成することができます

docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i http://petstore.swagger.io/v2/swagger.json -l python-flask -o /local/out/python_server

生成されたコードは以下の通り

  • ls -1 out/python_server/
Dockerfile
README.md
git_push.sh
requirements.txt
setup.py
swagger_server/
test-requirements.txt
tox.ini

Ruby の時とはだいぶことなっており Dockerfile もあります
requirements.txt があるのでそれを使って依存ライブラリをインストールします

  • pip3 install -r requirements.txt

グローバルインストールになるので必要であれば pipenv などを使って仮想環境を作ってください

クライアントコードの生成

クライアント側を生成するときは少し工夫が必要です
というのも Python の場合アクセスするホスト情報がハードコードされており、その元情報は swagger.json にある host になっています
なので一旦 swagger.json を手元にダウンロードしてから必要な部分を書き換えてコードを生成します

  • wget 'http://petstore.swagger.io/v2/swagger.json'
  • sed -i '.org' 's/petstore.swagger.io/localhost:8080/g' swagger.json
  • docker run --rm -v $(pwd):/local swaggerapi/swagger-codegen-cli generate -i /local/swagger.json -l python -o /local/out/python_client

こんな感じです
生成されたクライアントコードは以下の通りです

  • ls -1 out/python_client/
README.md
docs
git_push.sh
requirements.txt
sample.py
sample2.py
setup.py
swagger_client
test
test-requirements.txt
tox.ini

サーバの起動

  • cd out/python_server
  • python3 -m swagger_server

で OK です
localhost:8080 で起動します
localhost:8080/v2/ui で swagger ui が表示されます

サンプルコードの作成

ステータスを元にペットの情報を取得する API をコールしてみます

from __future__ import print_function
import swagger_client
from swagger_client.rest import ApiException

api_instance = swagger_client.PetApi()
status = ['available']

try:
    res = api_instance.find_pets_by_status(status)
    print(res)
except ApiException as e:
    print("Exception when calling PetApi->add_pet: %s\n" % e)

これで実行すると以下のようなエラーになります (一部省略)

ValueError: Invalid value for `name`, must not be `None`

原因はサーバサイドのコードが swagger.json の記載してある通りのレスポンスを返していないためです
今回はこれがちゃんと動くようにサーバ側のコードを修正してみたいと思います

サーバコードの修正

修正するコードは swagger_server/controllers/pet_controller.py になります
ここに find_pets_by_status(status) という関数があるのでこれを修正します

  • vim swagger_server/controllers/pet_controller.py
def find_pets_by_status(status):
    return [Pet(name='taro', photo_urls=['https://www.min-inuzukan.com/images/detailMain_pomeranian.png'])]

コメントなど関係ない部分はすべて削除しています
swagger.json を見るとわかりますが本来は Pet クラスの配列が返ってくるのが正しいです
なのでその通りになるようにレスポンスを返却します

これで再度サーバを起動してクライアントのサンプルコードを実行してみましょう
すると今度はエラーとならず正常にレスポンスが表示されると思います

[{'category': None,
 'id': None,
 'name': 'taro',
 'photo_urls': ['https://www.min-inuzukan.com/images/detailMain_pomeranian.png'],
 'status': None,
 'tags': None}]

少し解説

クライアントコード側の内部的な処理ですが、ざっくり説明するとサーバから取得した情報と swagger.json にある情報を元に必要な model or dictionary or Array or String etc… を生成します
find_pets_by_status の場合、swagger.json を見ると Pet の配列が返ってくることを想定しています
なので、クライアント側もサーバからのレスポンスを元に Pet モデルにバインドしようとします
Pet には namephoto_urls が必須パラメータとして定義されているためそれを含めた情報をサーバが返却する必要があります
また配列であることも想定しているのでたとえ要素が 1 つしかなくても配列で返却する必要があります

内部的には deserialize という関数がありそこでごにょごにょやっているので興味があれば見てください (swagger_client/api_client.py)
よくあるメタプログラミングを使っています

最後に

swagger-codegen を使って Python のコードをサーバ/クライアント側で生成してみました
また、実際にサーバを動作させてクライアントコードから問題なくコールできることを確認しました

Ruby のときもそうだったのですが、swagger-codegen は生成したコードを使って localhost で動作させるのに少し工夫が必要です
そもそも使用している swagger.json が外部のものなので localhost にアクセスするのを想定していないと言えばそれまでですが、テストなどではまずは localhost で動かしたくなります

今回は場合はクライアントコードの向き先とサーバコードのレスポンスの修正を行いました
実際は DB なども絡むので更に複雑になると思いますが最終的には swagger.json に定義されたレスポンス形式に落とし込む必要があるという点はしっかり抑えておきたい点かなと思います

0 件のコメント:

コメントを投稿