24
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

connexionを使ってPython APIサーバのAPI定義と実装を関連付ける

Last updated at Posted at 2019-04-02

connexion

connexionはOASを利用してAPIエンドポイントとpython関数をmapするためのpythonパッケージです。
Flaskベースで作成されており、通常flaskのdecorationで @app.route("/") などとエンドポイントを指定してHTTPリクエストと関数の関係を記述していたものを、OAS側に記載することができます。

何が嬉しいかというと、個人的にはAPI定義を元にルーティングが決定されるためAPI定義と実装の乖離が生じない点だと思います。あとはAPI定義に書いてあればValidationとかもしてくれるみたいです。

connexionは基本的にはFlaskで動作しますが他のpython webサーバでも動作します。
2019年4月現在、次のサーバが利用できるようです。

  • flask
  • tornado
  • aiohttp
  • gevent

使ってみる

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.3
BuildVersion:   18D109
$ python --version
Python 3.7.2

setup

repositoryに入っているexampleを動かしてみます。
今回はOpenAPI Specification v3.0で記述されたhelloworldを動かします。

$ git clone https://github.com/zalando/connexion.git
$ cd connexion/examples/openapi3/helloworld/
$ pip install connexion
$ pip show connexion | grep '^Version'
Version: 2.2.0

OASの中身は次のようになっています。
APIは1つだけエンドポイント/greeting/{name}を持ち、HTTP POSTリクエストを投げると"hello {name}"の文字列が返ってくることが記述されています。
特徴的なのはoperationIdで、この情報をもとにconnexionが呼び出す関数を決定します。

openapi/helloworld-api.yaml
openapi: "3.0.0"

info:
  title: Hello World
  version: "1.0"
servers:
  - url: http://localhost:9090/v1.0

paths:
  /greeting/{name}:
    post:
      summary: Generate greeting
      description: Generates a greeting message.
      operationId: hello.post_greeting
      responses:
        200:
          description: greeting response
          content:
            text/plain:
              schema:
                type: string
                example: "hello dave!"
      parameters:
        - name: name
          in: path
          description: Name of the person to greet.
          required: true
          schema:
            type: string
            example: "dave"

起動

起動してみます。

$ python hello.py
The swagger_ui directory could not be found.
    Please install connexion with extra install: pip install connexion[swagger-ui]
    or provide the path to your local installation by passing swagger_path=<your path>

The swagger_ui directory could not be found.
    Please install connexion with extra install: pip install connexion[swagger-ui]
    or provide the path to your local installation by passing swagger_path=<your path>

 * Serving Flask app "hello" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:9090/ (Press CTRL+C to quit)

この状態で次のようにHTTPリクエストを投げてやると、connexion(Flask)サーバがlocalhost:9090で待ち受けていて期待した通りのResponseが得られます。

$ curl -X POST http://localhost:9090/v1.0/greeting/hoge
Hello hoge

Warningに書いてあるように、商用環境で利用する際はWSGIサーバを利用してください。
http://flask.pocoo.org/docs/1.0/deploying/

中身の確認

hello.pyの中身は次のようになっています。やっていることは、

  1. connexionのFlaskAppインスタンスを作成
  2. openapi/helloworld-api.yamlからOASを読み込み
  3. サーバ起動
  4. operationId: hello.post_greetingのリクエストを捌く関数を定義
hello.py
#!/usr/bin/env python3

import connexion


def post_greeting(name: str) -> str:
    return 'Hello {name}'.format(name=name)

if __name__ == '__main__':
    app = connexion.FlaskApp(__name__, port=9090, specification_dir='openapi/')
    app.add_api('helloworld-api.yaml', arguments={'title': 'Hello World Example'})
    app.run()

flaskで書くとこんな感じでしょうか。
APPLICATION_ROOTが違いますが、開発用サーバがROOTを変えられないようなので大目に見てください(WSGI使いましょう)

hello.py
#!/usr/bin/env python3
import flask
app = flask.Flask(__name__)


@app.route('/greeting/<name>', methods=['POST'])
def post_greeting(name: str) -> str:
    return 'Hello {name}'.format(name=name)


if __name__ == '__main__':
    app.run(port=9090)

flaskではOASを読みに行かないためAPI定義とソースコード間に関連がないので、API定義を見ながら実装は独立してすることになります。
独立というと耳障りは良いですが実装はAPI仕様を反映しているべきで、そこを自動的に関連付けてくれることがconnexionのメリットなのかなと思います。

Tips

デフォルトのhostが違う

FlaskのApp.run()はhostパラメータを指定しなかった場合 127.0.0.1 でLISTENしますが、connexionは 0.0.0.0 でLISTENします。
大きな違いはないのですが、ちょっと不自然です。issueで質問しているのでわかったら書きます。

Flaskはここでhostパラメータ未指定の際のデフォルト値を指定しています
https://github.com/pallets/flask/blob/master/flask/app.py#L924

一方connexionも独自にデフォルト値を定義しているのでFlaskと違う挙動になっています。
https://github.com/zalando/connexion/blob/master/connexion/apps/flask_app.py#L83

FlaskAppに他サーバの実装が記述されている

この実装はどうなんだろう🤔

FlaskApp
        if self.server == 'flask':
            (snip)
        elif self.server == 'tornado':
            (snip)
        elif self.server == 'gevent':

まとめ

connexionを動かしてAPIと実装を分離してOASからその関連付けができることを見ました。
まだconnexion自身に怪しい部分がありますが、OASを書くなら使ってみてもいいかもしれません。

24
19
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
24
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?