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: "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
の中身は次のようになっています。やっていることは、
- connexionのFlaskAppインスタンスを作成
- openapi/helloworld-api.yamlからOASを読み込み
- サーバ起動
- operationId: hello.post_greetingのリクエストを捌く関数を定義
#!/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使いましょう)
#!/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に他サーバの実装が記述されている
この実装はどうなんだろう🤔
if self.server == 'flask':
(snip)
elif self.server == 'tornado':
(snip)
elif self.server == 'gevent':
まとめ
connexionを動かしてAPIと実装を分離してOASからその関連付けができることを見ました。
まだconnexion自身に怪しい部分がありますが、OASを書くなら使ってみてもいいかもしれません。