自己紹介
- pythonで雑ツール作っています
参考にしたスライド
https://qiita.com/yuji38kwmt/items/dfb929316a1335a161c0
https://qiita.com/yuji38kwmt/items/ad89fa0b8690627fea0f
https://github.com/kurusugawa-computer/annofab-api-python-client
## 話すこと
- OpenAPI Generatorを使ったきっかけ
- デフォルトのPythonクライアントコードでは使いづらかった部分
- OpenAPI Generatorでハマったことなど
OpenAPI Generatorを使ったきっかけ
背景
- 弊社で提供している「AnnoFab」というWebサービスのWebAPIを、Pythonで利用したい
OpenAPI Generatorを使う前の状態
WebAPIを利用しやすくするため、WebAPIごとにメソッドを作成していた。
def get_input_data(project_id: str, input_data_id: str):
pass
def put_input_data(project_id: str, input_data_id: str, request_body: Dict[str, Any]):
pass
...
手動でコーディングしていると、WebAPIの変更に追従していけなくなる
- パスパラメータの増減に対応できない
- WebAPIの追加/削除に対応できない
自動生成するために、OpenAPI Generatorを使った
- WebAPIのドキュメントは、OpenAPI Specを使ってReDocで生成されている。
- すでにOAS3で記載されたファイルがあったので、これを使ってPythonでWebAPIを実行するライブラリを作成した。
とりあえずPythonのクライントコードを出力してみる
生成コマンド
$ docker run --rm -u `id -u`:`id -g` -v ${PWD}:/local openapitools/openapi-generator-cli \
generate \
--input-spec /local/qiita.yaml \
--generator-name python \
--output /local/out
デフォルトの状態だと使いづらいと感じた部分
型ヒントが使えない
- 型ヒントとは、Pythonに型情報を与える記法
- https://www.hacky.xyz/entry/20180819/python-typehints
- Python 3.5から導入
- Pythonの古いバージョンにも対応しているため、型ヒントが使えない
- ライブラリはPython 2.7と3.4以上に対応している。
- 2020/03時点で最新は3.8。
- Python2系は2020/04にサポート終了
- Python3.4は2019/05にサポート終了
# 型ヒントがない状態
def get_input_data(project_id, input_data_id):
pass
return {}
# 型ヒントがある状態
def get_input_data(project_id: str, input_data_id: str) -> Dict[str, Any]:
pass
return {}
やっぱり型があると嬉しい。
メソッドの引数が多くなってしまう
- path parameter:1つずつ引数に渡す
- query parameter:1つずつ引数に渡す
paths:
/{country_id}/pets/:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: country_id
in: path
required: true
schema:
type: string
- name: limit
in: query
required: false
schema:
type: integer
format: int32
- name: name
in: query
required: true
schema:
type: string
api_response = api_instance.list_pets(country_id, name, limit=limit)
- 検索条件をquery parameterで表現するAPIは、メソッド引数が多くなる
- 感覚的ではあるが、path parameterとquery parameterは意識したい
連番を含むクラスが作成される
request_body, reponseのスキーマを$ref
を使わずに直接定義すると、InlineObjectXXX
やInlineResponseXXX
のように連番XXX
を含むクラスが作成される。
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
連番が含まれるクラス名を使うのはビミョー。
※yaml側を修正すればよいんだけど直接定義している部分がいくつかある
OASのtags
ごとにファイルが分けられる
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
弊社ツールでは、WebAPIのtagsがたまに変更されることがあるので、できるだけtags
に依存したくない。
自分好みのPythonクライアントライブラリを作る
- APIに対応したメソッドを一つのクラスにまとめる。OASの
tags
ごとにファイル/クラスを分けない。- OASの
tags
を意識しなくても、APIを利用できるようにするため
- OASの
- メソッド引数の
request_body
と、メソッドの戻り値はdict
にする。- 連番を含むquery_parameterを使わないようにするため
-
query_params
はdict
でメソッドに渡す。- メソッド引数が増えすぎないようにするため
- できるだけ型ヒントを使う。
ハマったこと/ツラかったこと
mustacheファイルに記載されている変数の意味が分からない
- ある程度は推測できるけど確証はないので、デバッグオプションで確認した
{{#operations}}
class {{classname}}(object):
def __init__(self, api_client=None):
if api_client is None:
api_client = ApiClient()
self.api_client = api_client
{{#operation}}
def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#allParams}}{{#required}}{{paramName}}, {{/required}}{{/allParams}}{{/sortParamsByRequiredFlag}}**kwargs): # noqa: E501
"""{{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501
### 処理を省略
{{/operation}}
{{/operations}}
mustacheファイルで記載するのツラい
- 制御構造がない
-
{{#xxx}}
という表記がif分岐なのか、for-eachなのか判断するには、変数名から予測するしかない - 見づらい
*
Handlebarなら書きやすい?
外部ファイルの参照がうまくいかない
-
components
配下を外部ファイルにして、それをインクルードしている- redoc-cliだと問題ない表記だった
https://github.com/OpenAPITools/openapi-generator/issues/3347
- redoc-cliだと問題ない表記だった
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
$ref: "./petstore-child.yaml"
schemas:
Pet:
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
皆さんにききたいこと
- クライアントコードをカスタマイズせずに使っています?
- mustacheファイルで使える変数はどうやって調べています?