OpenAPI Generatorで自分好みのpythonクライアントライブラリを生成する方法


# 自己紹介
* yuji38kwmt
* 名古屋のAIの会社でAIやってないエンジニア

![icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37903/680d1b06-83f5-fe83-9373-50411f05e6ff.png)


---
# WebAPIをどう実行していますか？
* curlコマンドですか？
* curlコマンドはつらくないですか？
* pythonで実行したくないですか？

---
# WebAPIをPythonでどう実行していますか？
* APIドキュメントを見ながら、自分で実装していますか？
* 似たようなコードがたくさんあって、飽きませんか？面倒でないですか？

---
# あなたが実行したいWebAPIは何で定義されていますか？
* OpenAPI Specification（OAS）ですか？
* OASで定義されているなら、あなたは幸せです。なぜならOASからソースを自動生成できるツールがあるからです。



---
# OpenAPI Specification（OAS）とは？

REST APIを記述するための言語です。
以下、「OAS」と略します。

>The OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for REST APIs,

https://github.com/OAI/OpenAPI-Specification 引用

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37903/fddefabd-710a-1960-5657-c1b568daa237.png)

---
# Qiita API v2をOAS3で記述する
例として、Qiita APIの[タグ一覧を返す](https://qiita.com/api/v2/docs#get-apiv2tags) APIをOASで表現します。
※QiitaはOASで記載されたファイルを公開していません。以下のファイルは非公式です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37903/8a27018f-87a5-3d03-dab0-29d275c21dc2.png)

---




```yaml:qiita.yaml
openapi: "3.0.0"
info:
  version: 0.1.0
  title: Qiita API
servers:
  - url: https://qiita.com/api/v2
paths:
  /tags:
    get:
      tags:
      - tags
      summary: タグ一覧を作成日時の降順で返します。
      operationId: getTagList
      parameters:
        - name: page
          in: query
          description: ページ番号 (1から100まで)
          example: "1"
          schema:
            type: string
        - name: per_page
          in: query
          description: 1ページあたりに含まれる要素数 (1から100まで)
          example: "20"
          schema:
            type: string
        - name: sort
          in: query
          description: 並び順 (countで記事数順、nameで名前順)
          example: "count"
          schema:
            type: string
            enum:
            - count
            - name
      responses:
        200:
          description: タグ一覧
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Tag"
components:
  schemas:
    Tag:
      properties:
        followers_count:
          description: このタグをフォローしているユーザの数
          example: 100
          type: integer
        icon_url:
          description: このタグに設定されたアイコン画像のURL
          example: "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/9de6a11d330f5694820082438f88ccf4a1b289b2/medium.jpg"
          type: string
        id:
          description: タグを特定するための一意な名前
          example: "qiita"
          type: string
        items_count:
          description: このタグが付けられた記事の数
          example: 200
          type: integer
```

---
# OpenAPI Generatorとは？
OASからAPIのクライアントライブラリやサーバのスタブ、ドキュメントなどを生成します。

>OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (both 2.0 and 3.0 are supported).

https://github.com/OpenAPITools/openapi-generator


---
## OpenAPI Generatorが対応している言語/フレームワーク
Pythonクライアントライブラリや、Flaskのスタブが生成できます。
※マイナーな言語にも対応されています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37903/b8f9abed-81fa-ee97-56d1-f73ef6f109e5.png)

---
## とりあえずOpenAPI GeneratorでPythonクライアントライブラリを生成する
以下のコマンドを実行すると、`qiita.yaml`から生成されたPythonクライアントライブラリが、`out`ディレクトリに出力されます。


```
$ 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クライアントライブラリのフォルダ構成
ライブラリ用のpythonファイルだけでなく、ドキュメントやテストコード、設定ファイルなど、ライブラリに必要なファイル一式が生成されます。

```
$ tree -a
.
├── docs
│   ├── Tag.md
│   └── TagsApi.md
├── .gitignore
├── git_push.sh
├── openapi_client
│   ├── api
│   │   ├── __init__.py
│   │   └── tags_api.py
│   ├── api_client.py
│   ├── configuration.py
│   ├── exceptions.py
│   ├── __init__.py
│   ├── models
│   │   ├── __init__.py
│   │   └── tag.py
│   └── rest.py
├── .openapi-generator
│   └── VERSION
├── .openapi-generator-ignore
├── README.md
├── requirements.txt
├── setup.py
├── test
│   ├── __init__.py
│   ├── test_tag.py
│   └── test_tags_api.py
├── test-requirements.txt
├── tox.ini
└── .travis.yml
```


---
## 生成したクライアントライブラリを実行する


```python
import openapi_client
from pprint import pprint

access_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
api_client = openapi_client.ApiClient(header_name="Authorization", header_value="Bearer " + access_token)

# タグの一覧を取得する
tags_api_instance = openapi_client.TagsApi(api_client)
tag_list = tags_api_instance.get_tag_list(per_page=15)
pprint(tag_list)
# [{'followers_count': 0, 'icon_url': None, 'id': 'Broadcas', #'items_count': 6},
# ...
```

---
# 自分好みにクライアントライブラリをカスタマイズする


---
## カスタマイズしていないクライアントライブラリのイケていない部分


* 型ヒントが使われていない（Python2, Python3.4以下をサポートしているため）
* query parameterの数だけメソッドの引数が増える（query_parameterをまとめて渡したい）

---
## テンプレートファイルを修正してカスタマイズする
OpenAPI Generatorは、mustache記法で記載されたテンプレートファイルを修正して、カスタマイズできます。


```mustache:api.mustache

{{#operations}}
{{#operation}}

    def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#pathParams}}{{paramName}}: {{dataType}}, {{/pathParams}}{{/sortParamsByRequiredFlag}}{{#hasQueryParams}}query_params: Optional[Dict[str, Any]] = None, {{/hasQueryParams}}{{#hasBodyParam}}request_body: Optional[Any] = None{{/hasBodyParam}}) -> Any:  # noqa: E501
        """
        {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}}  # noqa: E501
        """

        url_path = f'{{{path}}}'
        http_method = '{{httpMethod}}'
        keyword_params: Dict[str, Any] = {
{{#hasQueryParams}}
            'query_params': query_params,
{{/hasQueryParams}}
{{#hasBodyParam}}
            'request_body': request_body,
{{/hasBodyParam}}
        }
        return self._request_wrapper(http_method, url_path, **keyword_params)

{{/operation}}
{{/operations}}
```

---
### mustacheとは？

* mustacheはロジックがないテンプレート（Logic-less）
* ロジックがないとは、if/elseやloopなどの制御構造がないこと


>Mustache is described as a "logic-less" system because it lacks any explicit control flow statements, like if and else conditionals or for loops; however, both looping and conditional evaluation can be achieved using section tags processing lists and lambdas.

https://en.wikipedia.org/wiki/Mustache_(template_system) 引用

---
### なぜmustache（口髭）という名前？
横向きの口髭に似ている波括弧をたくさん使うから。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37903/266d25c0-f536-0e6c-76fc-226a0a80c5fb.png)


>It is named "Mustache" because of heavy use of braces, { }, that resemble a sideways moustache.

https://en.wikipedia.org/wiki/Mustache_(template_system) 引用


---
## できあがったライブラリ
手動で生成したファイルと連結したら、sedで置換したりして、自分好みのライブラリを作成できました。

```
### Qiita APIすべてが定義されたクラスのインスタンスを生成
api_instance = QiitaApi(access_token)

### タグの一覧を取得する
query_params = {"per_page":15, "sort": "name"}
tag_list = api_instance.get_tag_list(query_params=query_params)
```

---
# まとめ
* OpenAPI Generatorを使うと、OpenAPI Specification（OAS）でRESTが表現されたAPIのクライアントライブラリが生成できる
* カスタマイズする場合は、msutache(口髭）ファイルを修正する



---
## 補足：自動生成は結構ハマる
完全に自動生成するのは難しい。
ハマった場合の対応方法は以下の3つかな？

* 泥臭い手法を取る（sed, awk）
* 自動生成ツールに手を入れる（contributorになる）
* 諦める