Flask-RESTX で作成した API を Postman のコレクション形式でエクスポートする

Flask-RESTX には API を Postman のコレクションとしてエクスポートする機能がありますが、出力するのに苦労したので、調査の内容を記録しておきます。(出力したファイルを Postman にインポートするのにも変換が必要だったので、インポート手順もついでに記載します。）

Flask-RESTX で作成したアプリケーションは別記事で紹介しております。こちらのアプリケーションで作成した API を Postman のコレクションとして出力してみます。

https://qiita.com/kiyo27/items/d928f65b215d914f1979

## Postman 用のコードを記載

公式の doc に Postman のコレクションとして出力するコードが紹介されていますが、コードをそのままコピーしてもエラーが出て動きません。。

https://flask-restx.readthedocs.io/en/latest/postman.html

``This has to be executed when application context is available.``とでて ``application context``で実行されていないのでエラーが発生しているっぽいです。

```
    return url_for(self.endpoint("root"), _scheme=self.url_scheme, _external=True)
  File "C:\Users\kiyo27\.pyenv\pyenv-win\versions\3.8.10\lib\site-packages\flask\helpers.py", line 274, in url_for 
    raise RuntimeError(
RuntimeError: Attempted to generate a URL without the application context being pushed. This has to be executed when application context is available.
```

ネットで調べてみると[こちらの issue](https://github.com/noirbizarre/flask-restplus/issues/208) が出てきました。``with app.app_context():`` を追加して実行してみます。 

```python
from flask import Flask, json
from apis import api

app = Flask(__name__)

def see_json():
    urlvars = False  # Build query strings in URLs
    swagger = True  # Export Swagger specifications
    data = api.as_postman(urlvars=urlvars, swagger=swagger)
    # print(json.dumps(data))
    f = open('postman_import.json', 'w')
    f.write(json.dumps(data))

with app.app_context():
    see_json()
```

``url_for`` でエラーが起きたりします。

```
    return url_for(self.endpoint("root"), _scheme=self.url_scheme, _external=True)
  File "C:\Users\kiyo27\.pyenv\pyenv-win\versions\3.8.10\lib\site-packages\flask\helpers.py", line 300, in url_for 
    raise RuntimeError(
RuntimeError: Application was not able to create a URL adapter for request independent URL generation. You might be able to fix this by setting the SERVER_NAME config variable.
```

エラーが発生しているコードを調べると、

```python
    def endpoint(self, name):
        if self.blueprint:
            return "{0}.{1}".format(self.blueprint.name, name)
        else:
            return name

...

    @property
    def base_path(self):
        """
        The API path

        :rtype: str
        """
        return url_for(self.endpoint("root"), _external=False)
```

うーーん、api を分割して記述している場合は、 ``blueprint`` を使って書き直す必要があるかもしれません。


色々調査して直したコードが書きになります。


```python:postman.py
from flask import Flask, json, url_for, Blueprint
from flask_restx import Api
from apis import api
from apis.todos import api as todos

app = Flask(__name__)
app.config['SERVER_NAME'] = 'localhost'
blueprint = Blueprint('api', __name__, url_prefix='/api')
api = Api(blueprint, doc='/doc/')
api.add_namespace(todos, path='/todos')
app.register_blueprint(blueprint)

def see_json():
    # print(url_for('api.doc'))
    urlvars = False  # Build query strings in URLs
    swagger = True  # Export Swagger specifications
    data = api.as_postman(urlvars=urlvars, swagger=swagger)
    # print(json.dumps(data))
    f = open('postman_import.json', 'w')
    f.write(json.dumps(data))

with app.app_context():
    see_json()
```

``api.todos`` の ``api`` は、

```python:api/todos.py
from flask_restx import Namespace, Resource, fields

api = Namespace('todos', description='TODO operations')
```

こちらで実行したら、無事に出力されました。

## Postman にインポートする

flask-restx で出力した postman のコレクションは **v1** なので、インポートできませんでした。ですが幸いにも v1 から v2 に変換する方法が公式の doc に書いてありましたのでそちらを実行します。

https://learning.postman.com/docs/getting-started/importing-and-exporting-data/#converting-postman-collections-from-v1-to-v2

**Postman collection transformer** をインストール:

```terminal
sudo npm install -g postman-collection-transformer
```

v1 から v2 に変換:

```terminal
postman-collection-transformer convert -i postman_import.json -o postman_v2.json -j 1.0.0 -p 2.0.0 -P
```

これで postman にインポートできるようなります。

![postman-import.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/780549/be5bbb8c-657e-31f1-704c-aab93da64f5f.png)