Flask RESTPlusでAPIサーバを作ってSwaggerUIを自動生成する

Pythonの[Flask-RESTPlus](https://flask-restplus.readthedocs.io/en/stable/index.html)を使うとAPIサーバのコードにドキュメント(仕様書)を書けば自動で[SwaggerUI](https://swagger.io/tools/swagger-ui/)で生成されるので色々試してみました。

# 1. テンプレート

ここで使うテンプレートです。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app)

# ここにドキュメントとルートの処理を書く

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)
```

# 2. 接続先

上記テンプレートで起動すると `http://flask server ip or FQDN:5000` へアクセスすれば自動でSwaggerUIでAPIドキュメントが表示されます。

# 3. APIドキュメント例

APIドキュメントについてですが、指定したドキュメントをSwaggerUIで表示させたい場合は対象の部分をデコレートするだけです。
例えばclassよりも前に書けばclass全体に指定したドキュメントが表示されます。
メソッドの前に書けば、書いたメソッドにだけ指定したドキュメントが表示されます。
何はともあれ、一先ずやってみます。

## 3-1. URLパラメーター説明

例えば `id` `param` というパラメーターのドキュメントを `@api.doc` を使うことで自動生成可能です。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app)

@api.route('/example/<id>/<param>')
@api.doc(params={'id': 'id of example','param':'param of example'})
class Example(Resource):
    def get(self, id, param):
        return {'id': id, 'param': param}

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)
```

**結果**

![flask_restplus_ex01.gif](https://qiita-image-store.s3.amazonaws.com/0/180118/42fa9861-e4f1-a47c-f232-f96e6d79d5f4.gif)

## 3-2. レスポンスの説明

ここでは、レスポンスの説明を付け加えてみます。
`@api.marshal_with` を使えばレスポンス仕様を表示できます。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app)

example_get_spec = api.model('Example GET', {
    'id': fields.String(description='example id'),
    'param': fields.String(description='example param')
})

@api.route('/example/<id>/<param>')
@api.doc(params={'id': 'id of example','param':'param of example'})
class Example(Resource):
    @api.marshal_with(example_get_spec)
    def get(self, id, param):
        return {'id': id, 'param': param}

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)
```

**結果**

![flask_restplus_ex02.gif](https://qiita-image-store.s3.amazonaws.com/0/180118/0f9ad212-80e8-1fe8-7fb6-28d98cf05adb.gif)

`X-Fields` はマスクするフィールドを指定します。
例えば `id` を入力して実行すると `id` の情報しかレスポンスに返ってきません。

## 3-3. データリクエスト説明

例えばJSONデータをPOSTする時のデータ構造を説明するドキュメントを以下のように追加(example2)してみます。
`@api.expect` を使うとJSONデータをSwaggerUIからリクエストとして出すことが出来ます。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app)

example_get_spec = api.model('Example GET', {
    'id': fields.String(description='example id'),
    'param': fields.String(description='example param')
})

@api.route('/example/<id>/<param>')
@api.doc(params={'id': 'id of example','param':'param of example'})
class Example(Resource):
    @api.marshal_with(example_get_spec)
    def get(self, id, param):
        return {'id': id, 'param': param}

example2_post_spec = api.model('Example POST', {
    'name': fields.String(description='name of example2'),
    'param': fields.String(description='param of example2')
})

@api.route('/example2')
@api.expect(example2_post_spec)
class Example2(Resource):
    @api.marshal_with(example2_post_spec)
    def post(self):
        return {'name': request.json['name'], 'param': request.json['param']}

if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)
```

**結果**

![flask_restplus_ex03.gif](https://qiita-image-store.s3.amazonaws.com/0/180118/202f9781-238e-4206-e027-433a7c31b608.gif)

## 3-4. APIドキュメントのバージョンやタイトルを変更する

`Api` の引数にバージョンやタイトルを指定すれば変更できます。
`doc` はアクセスするURLを変更する事ができます。
以下の例では `http://flask server ip or FQDN:5000/doc/` へアクセスすればドキュメントが表示されるようにしています。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app, version='2.0', title='Example API Document', description='', doc='/doc/',
          default='Example', default_label='default label')
(snip)
```

**結果**

![スクリーンショット 2018-09-08 22.27.29.png](https://qiita-image-store.s3.amazonaws.com/0/180118/5a8e8dda-f089-9198-8826-28b3526f3ffd.png)


## 3-5. NameSpaceを分ける

NameSpaceを分ける事で目的毎に仕様が書けたりします。

```python
#!/usr/bin/env python3
from flask import Flask, request
from flask_restplus import Api, Resource, fields, Namespace

app = Flask(__name__)
api = Api(app, version='2.0', title='Example API Document', description='', doc='/doc/',
          default='Example', default_label='default label')

api.add_namespace(Namespace('Example2', description='example2'))
api.add_namespace(Namespace('Example3', description='example3'))
(snip)
```

**結果**

![スクリーンショット 2018-09-08 22.31.13.png](https://qiita-image-store.s3.amazonaws.com/0/180118/8cfe345b-48a2-c277-49e5-143163c0eeb2.png)

以下のドキュメントに詳しく書いてあります。

https://flask-restplus.readthedocs.io/en/stable/scaling.html

# 4. 最後に

コードにドキュメントを書いてそのままSwaggerUIで表示されるのは便利ですね。
ブラウザでAPIの動作確認が簡単に出来るのもいいですね。
いちいち他(Wordとか)のドキュメントで残すというのはコストが高いので、なるべくコードに書いてそのままドキュメント化されるような効率化が必要だと感じる今日この頃です。

# 5. 参考

https://flask-restplus.readthedocs.io/en/stable/swagger.html
http://flask.pocoo.org/docs/1.0/api/