LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

OpenAPI Generatorを使ったきっかけ、ハマったことなど

Last updated at Posted at 2020-03-08
1 / 23

自己紹介

  • 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で利用したい

image.png


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に型情報を与える記法
  • 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を使わずに直接定義すると、InlineObjectXXXInlineResponseXXXのように連番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を利用できるようにするため
  • メソッド引数のrequest_bodyと、メソッドの戻り値はdictにする。
    • 連番を含むquery_parameterを使わないようにするため
  • query_paramsdictでメソッドに渡す。
    • メソッド引数が増えすぎないようにするため
  • できるだけ型ヒントを使う。

ハマったこと/ツラかったこと


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なら書きやすい?


外部ファイルの参照がうまくいかない

petstore-parent.yaml
      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"
petstore-child.yaml
schemas:
  Pet:
    required:
      - id
      - name
    properties:
      id:
        type: integer
        format: int64
      name:
        type: string
      tag:
        type: string

皆さんにききたいこと

  • クライアントコードをカスタマイズせずに使っています?
  • mustacheファイルで使える変数はどうやって調べています?
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up