如何生成用 flask-restplus 编写的现有 API 的机器可读 yaml 规范?

How to generate a machine readable yaml specification of an existing API written in flask-restplus?

我有一个简单的 API 在 flask-restplus 的帮助下编写的:

from flask import Flask
from flask_restplus import Resource, Api

app = Flask(__name__)                  #  Create a Flask WSGI application
api = Api(app)                         #  Create a Flask-RESTPlus API

@api.route('/hello')                   #  Create a URL route to this resource
class HelloWorld(Resource):            #  Create a RESTful resource
    def get(self):                     #  Create GET endpoint
        return {'hello': 'world'}

if __name__ == '__main__':
    app.run(debug=True) 

当我在浏览器中导航到 loacalhost:5000/ 时,我得到了一个基本的 Swagger 文档,但我找不到我在哪里可以获得 API 的机器可读的纯 yaml 表示,应该'是不是也自动生成了?

我在官方 flask-restplus docs 中找不到有关 "Swagger Yaml documentation generation" 的任何信息。因此,我决定检查源代码,发现 Swagger class 为 API 实例实现了 Swagger 文档生成。

flask-restplus 源代码中的 Swagger class 是 API 实例的 Swagger 文档包装器。 class 中的所有方法都建议将 API 数据序列化为 JSON 字典。例如,考虑此 class 的 as_dict() 函数,它将完整的 Swagger 规范序列化为可序列化的字典。查看此函数的文档字符串:

from flask import Flask
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger

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

swag = Swagger(api)
print(swag.as_dict.__doc__) 

#Output:
Output the specification as a serializable ``dict``.

    :returns: the full Swagger specification in a serializable format
    :rtype: dict

我可能错了,但源代码表明 API 文档仅作为 JSON 返回,默认情况下在 http://localhost:5000/swagger.json 可用。我找不到 YAML 的任何内容。

但是有一个解决方法可以为您的 API 生成 YAML 文档。我使用 jsonyaml 库将 /swagger.json 的 json 响应转储到 YAML 中并将其保存到 yamldoc.yml。您可以通过转到 http://localhost:5000/swagger.yml 来调用它。完整代码:

from flask import Flask
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger
import requests
import json, yaml

app = Flask(__name__)                  #  Create a Flask WSGI application
api = Api(app)                         #  Create a Flask-RESTPlus API

@api.route('/hello')                   #  Create a URL route to this resource
class HelloWorld(Resource):            #  Create a RESTful resource
    def get(self):                  
        return {'hello': 'world'}

@api.route('/swagger.yml')
class HelloWorld(Resource):    
    def get(self):
       url = 'http://localhost:5000/swagger.json'       
       resp = requests.get(url)
       data = json.loads(resp.content)    
       with open('yamldoc.yml', 'w') as yamlf:
           yaml.dump(data, yamlf, allow_unicode=True)
       return {"message":"Yaml document generated!"}


if __name__ == '__main__':
    app.run(debug=True) 

希望对您有所帮助。

根据@amanb 的回答,我将我的 api return 制作成了一个 yaml 文件,但没有提出任何请求。 根据 flask restplus 的文档(或者,最近的分支,flask restx) it is possible to export the Swagger specififcations corresponding to your API 使用:

from flask import json

from myapp import api

print(json.dumps(api.__schema__))

所以,我更喜欢使用 api.__schema__

而不是使用 requests

由于我的目标是在请求时提供文件下载,所以需要使用Flasksend_file功能。此外,此文件稍后可以从目录中删除,因此我们可以使用 Flaskafter_this_request 装饰器来调用将删除该文件的注释函数。完整代码:

import os
import json
import yaml

from flask import Flask, after_this_request, send_file, safe_join, abort
from flask_restplus import Resource, Api
from flask_restplus.api import Swagger


app = Flask(__name__)                  #  Create a Flask WSGI application
api = Api(app)                         #  Create a Flask-RESTPlus API

@api.route('/hello')                   #  Create a URL route to this resource
class HelloWorld(Resource):            #  Create a RESTful resource
    def get(self):                  
        return {'hello': 'world'}

@api.route('/swagger.yml')
class HelloWorld(Resource):    
    def get(self):
       data = json.loads(json.dumps(api.__schema__))
       with open('yamldoc.yml', 'w') as yamlf:
           yaml.dump(data, yamlf, allow_unicode=True, default_flow_style=False)
           file = os.path.abspath(os.getcwd())
           try:
               @after_this_request
               def remove_file(resp):
                   try:
                       os.remove(safe_join(file, 'yamldoc.yml'))
                   except Exception as error:
                       log.error("Error removing or closing downloaded file handle", error)
                   return resp

               return send_file(safe_join(file, 'yamldoc.yml'), as_attachment=True, attachment_filename='yamldoc.yml', mimetype='application/x-yaml')
           except FileExistsError:
               abort(404)


if __name__ == '__main__':
    app.run(debug=True)