flask restplus swagger

Swagger API 文档是自动生成的,可从您的 API(path='/) 的 path 对应的值获取。@api.doc()您可以使用装饰器配置文档。

使用@api.doc()装饰器记录

装饰器api.doc()允许您在文档中包含其他信息。可以装饰一个类或方法:

1
2
3
4
5
6
7
8
9
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}

@api.doc(responses={403: 'Not Authorized'})
def post(self, id):
api.abort(403)

自动记录模型

model()所有使用、实例化的模型clone()都inherit() 将自动记录在您的 Swagger 规范中。可以在 swagger ui 中看到。

model().inherit() 方法将在 Swagger 模型定义中注册父级和子级:

1
2
3
4
5
6
7
8
parent = api.model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
})

child = api.inherit('Child', parent, {
'extra': fields.String
})

上述配置将产生这些 Swagger 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"Parent": {
"properties": {
"name": {"type": "string"},
"class": {"type": "string"}
},
"discriminator": "class",
"required": ["class"]
},
"Child": {
"allOf": [
{
"$ref": "#/definitions/Parent"
}, {
"properties": {
"extra": {"type": "string"}
}
}
]
}
}

@api.marshal_with()装饰器

这个装饰器像原始marshal_with()装饰器一样工作,不同之处在于它记录了方法。可选参数code允许您指定预期的 HTTP 状态代码(默认为 200)。可选参数as_list允许您指定对象是否作为列表返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>', endpoint='my-resource')
class MyResource(Resource):
@api.marshal_with(resource_fields, as_list=True)
def get(self):
return get_objects()

@api.marshal_with(resource_fields, code=201)
def post(self):
return create_object(), 201

Api.marshal_list_with()装饰器等价于.Api.marshal_with(fields, as_list=True)()

@api.expect()装饰器 等效于 @api.doc(body=resource_fields)

装饰器@api.expect()允许您指定预期的输入字段。它接受一个可选的布尔参数validate,指示是否应验证有效负载。可以通过将配置RESTPLUS_VALIDATE设置为True 或传递validate=True给 API 构造函数来全局自定义验证行为。

以下示例是等效的:

  • 使用@api.expect()装饰器:
1
2
3
4
5
6
7
8
9
resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.expect(resource_fields)
def get(self):
pass
  • 使用api.doc()装饰器:
1
2
3
4
5
6
7
8
9
resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.doc(body=resource_fields)
def get(self):
pass

您可以将列表指定为预期输入:

1
2
3
4
5
6
7
8
9
resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.expect([resource_fields])
def get(self):
pass

您可以使用RequestParser来定义预期的输入:

1
2
3
4
5
6
7
8
9
10
parser = api.parser()
parser.add_argument('param', type=int, help='Some param', location='form')
parser.add_argument('in_files', type=FileStorage, location='files')


@api.route('/with-parser/', endpoint='with-parser')
class WithParserResource(restplus.Resource):
@api.expect(parser)
def get(self):
return {}

可以在特定端点上启用或禁用验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation disabled
@api.expect(resource_fields)
def post(self):
pass

# Payload validation enabled
@api.expect(resource_fields, validate=True)
def post(self):
pass

通过配置进行应用程序范围验证的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
app.config['RESTPLUS_VALIDATE'] = True

api = Api(app)

resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation enabled
@api.expect(resource_fields)
def post(self):
pass

# Payload validation disabled
@api.expect(resource_fields, validate=False)
def post(self):
pass

通过构造函数进行应用程序范围验证的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
api = Api(app, validate=True)

resource_fields = api.model('Resource', {
'name': fields.String,
})

@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation enabled
@api.expect(resource_fields)
def post(self):
pass

# Payload validation disabled
@api.expect(resource_fields, validate=False)
def post(self):
pass

使用@api.response()装饰器记录 等效于 @api.doc(responses='...')

装饰器@api.response()允许您记录已知响应,并且是@api.doc(responses='...').

以下两个定义是等价的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@api.route('/my-resource/')
class MyResource(Resource):
@api.response(200, 'Success')
@api.response(400, 'Validation Error')
def get(self):
pass


@api.route('/my-resource/')
class MyResource(Resource):
@api.doc(responses={
200: 'Success',
400: 'Validation Error'
})
def get(self):
pass

您可以选择将响应模型指定为第三个参数:

1
2
3
4
5
6
7
8
9
model = api.model('Model', {
'name': fields.String,
})

@api.route('/my-resource/')
class MyResource(Resource):
@api.response(200, 'Success', model)
def get(self):
pass

@api.marshal_with()装饰器自动记录响应:

1
2
3
4
5
6
7
8
9
10
model = api.model('Model', {
'name': fields.String,
})

@api.route('/my-resource/')
class MyResource(Resource):
@api.response(400, 'Validation error')
@api.marshal_with(model, code=201, description='Object created')
def post(self):
pass

可以在不知道响应代码的情况下指定发送的默认响应:

1
2
3
4
5
@api.route('/my-resource/')
class MyResource(Resource):
@api.response('default', 'Error')
def get(self):
pass

@api.route()装饰器

doc您可以使用 的参数提供类范围的文档Api.route()Api.doc()此参数接受与装饰器相同的值。

例如,这两个声明是等价的:

  • 使用@api.doc()
1
2
3
4
5
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}
  • 使用@api.route()
1
2
3
4
@api.route('/my-resource/<id>', endpoint='my-resource', doc={'params':{'id': 'An ID'}})
class MyResource(Resource):
def get(self, id):
return {}

每个资源有多个路由

多个Api.route()装饰器可用于为Resource. 该doc参数提供每条路线的文档。

例如,这里description仅适用于/also-my-resource/<id>路线:

1
2
3
4
5
6
7
8
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={"description": "Alias for /my-resource/<id>"},
)
class MyResource(Resource):
def get(self, id):
return {}

在这里,该/also-my-resource/<id>路线被标记为已弃用:

1
2
3
4
5
6
7
8
9
10
11
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={
"description": "Alias for /my-resource/<id>, this route is being phased out in V2",
"deprecated": True,
},
)
class MyResource(Resource):
def get(self, id):
return {}

除非明确覆盖,否则应用于Resourceusing的文档在所有路由Api.doc()之间共享:

1
2
3
4
5
6
7
8
9
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={"description": "Alias for /my-resource/<id>"},
)
@api.doc(params={"id": "An ID", description="My resource"})
class MyResource(Resource):
def get(self, id):
return {}

在这里,id来自装饰器的文档@api.doc()存在于两个路由中, 从 装饰器 /my-resource/<id>继承描述并用.My resource``@api.doc()``/also-my-resource/<id>``Alias for /my-resource/<id>

带有doc参数的路线被赋予一个独特的Swagger operationId。没有参数的路由 doc具有相同的 Swagger operationId,因为它们被认为是相同的操作。

记录字段

每个 Flask-Restplus 字段都接受用于记录该字段的可选参数:

  • required:一个布尔值,指示该字段是否始终设置(默认False值:)
  • description:有关该字段的一些详细信息(默认None值:)
  • example:显示时使用的示例(_默认_:)None

还有特定于字段的属性:

  • String字段接受以下可选参数:

    • enum:限制授权值的数组。
    • min_length:预期的最小长度。
    • max_length:预期的最大长度。
    • pattern: 用于验证字符串的 RegExp 模式。
  • Integer和字段Float接受以下Arbitrary可选参数:

    • min: 限制可接受的最小值。
    • max: 限制最大接受值。
    • exclusiveMin: 如果True, 最小值不在允许的区间内。
    • exclusiveMax: 如果True, 最大值不在允许的区间内。
    • multiple:指定数字必须是该值的倍数。
  • The DateTime field accepts the min, max, exclusiveMin and exclusiveMax optional arguments. These should be dates or datetimes (either ISO strings or native objects).

1
2
3
4
5
my_fields = api.model('MyModel', {
'name': fields.String(description='The name', required=True),
'type': fields.String(description='The object type', enum=['A', 'B']),
'age': fields.Integer(min=0),
})

记录方法

每个资源都将记录为 Swagger 路径。

每个资源方法 ( get, post, put, delete, path, options, head) 都将记录为 Swagger 操作。

operationId您可以使用id关键字参数指定唯一的 Swagger :

1
2
3
4
5
@api.route('/my-resource/')
class MyResource(Resource):
@api.doc(id='get_something')
def get(self):
return {}

您也可以将第一个参数用于相同目的:

1
2
3
4
5
@api.route('/my-resource/')
class MyResource(Resource):
@api.doc('get_something')
def get(self):
return {}

如果未指定,operationId则使用以下模式提供默认值:

1
{{verb}}_{{resource class name | camelCase2dashes }}

在前面的示例中,默认生成的operationIdget_my_resource.

operationId您可以通过为参数提供可调用对象来覆盖默认生成器default_id。此可调用对象接受两个位置参数:

  • 资源类名
  • HTTP 方法(小写)
1
2
3
4
def default_id(resource, method):
return ''.join((method, resource))

api = Api(app, default_id=default_id)

在前面的示例中,生成的operationId将是getMyResource.

每个操作都会自动接收命名空间标签。如果资源附加到根 API,它将接收默认命名空间标签。

方法参数

URL 路径中的参数会自动记录。您可以使用装饰器的params关键字参数提供其他信息:api.doc()

1
2
3
4
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
pass

或使用api.param快捷方式装饰器:

1
2
3
4
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.param('id', 'An ID')
class MyResource(Resource):
pass

输入和输出模型

您可以使用装饰器的model关键字参数指定序列化输出模型。api.doc()

对于POSTPUT方法,使用body关键字参数来指定输入模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fields = api.model('MyModel', {
'name': fields.String(description='The name', required=True),
'type': fields.String(description='The object type', enum=['A', 'B']),
'age': fields.Integer(min=0),
})


@api.model(fields={'name': fields.String, 'age': fields.Integer})
class Person(fields.Raw):
def format(self, value):
return {'name': value.name, 'age': value.age}


@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
@api.doc(model=fields)
def get(self, id):
return {}

@api.doc(model='MyModel', body=Person)
def post(self, id):
return {}

如果同时使用bodyformData参数,将引发 SpecsError

模型也可以用RequestParser.

1
2
3
4
5
6
7
8
9
parser = api.parser()
parser.add_argument('param', type=int, help='Some param', location='form')
parser.add_argument('in_files', type=FileStorage, location='files')

@api.route('/with-parser/', endpoint='with-parser')
class WithParserResource(restplus.Resource):
@api.expect(parser)
def get(self):
return {}

解码后的有效负载将作为请求上下文中有效负载属性中的字典提供。

1
2
3
4
@api.route('/my-resource/')
class MyResource(Resource):
def get(self):
data = api.payload

Using RequestParser 优于api.param()装饰器来记录表单字段,因为它也执行验证。

标题

@api.header()您可以使用装饰器快捷方式记录响应标头。

1
2
3
4
5
6
@api.route('/with-headers/')
@api.header('X-Header', 'Some class header')
class WithHeaderResource(restplus.Resource):
@api.header('X-Collection', type=[str], collectionType='csv')
def get(self):
pass

如果您需要指定仅出现在 gvien 响应中的标头,只需使用@api.response headers参数。

1
2
3
4
5
@api.route('/response-headers/')
class WithHeaderResource(restplus.Resource):
@api.response(200, 'Success', headers={'X-Header': 'Some header'})
def get(self):
pass

记录预期/请求标头是通过@api.expect装饰器完成的

1
2
3
4
5
6
7
8
parser = api.parser()
parser.add_argument('Some-Header', location='headers')

@api.route('/expect-headers/')
@api.expect(parser)
class ExpectHeaderResource(restplus.Resource):
def get(self):
pass

级联

方法文档优先于类文档,继承文档优先于父文档。

例如,这两个声明是等价的:

  • 类文档由方法继承:
1
2
3
4
5
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.params('id', 'An ID')
class MyResource(Resource):
def get(self, id):
return {}
  • 类文档被特定于方法的文档覆盖:
1
2
3
4
5
6
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.param('id', 'Class-wide description')
class MyResource(Resource):
@api.param('id', 'An ID')
def get(self, id):
return {}

您还可以从类装饰器中提供特定于方法的文档。以下示例将生成与前两个示例相同的文档:

1
2
3
4
5
6
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.params('id', 'Class-wide description')
@api.doc(get={'params': {'id': 'An ID'}})
class MyResource(Resource):
def get(self, id):
return {}

标记为已弃用 @api.deprecated

@api.deprecated您可以使用装饰器将资源或方法标记为已弃用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Deprecate the full resource
@api.deprecated
@api.route('/resource1/')
class Resource1(Resource):
def get(self):
return {}

# Deprecate methods
@api.route('/resource4/')
class Resource4(Resource):
def get(self):
return {}

@api.deprecated
def post(self):
return {}

def put(self):
return {}

隐藏文档

您可以使用以下任何方法从文档中隐藏一些资源或方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Hide the full resource
@api.route('/resource1/', doc=False)
class Resource1(Resource):
def get(self):
return {}

@api.route('/resource2/')
@api.doc(False)
class Resource2(Resource):
def get(self):
return {}

@api.route('/resource3/')
@api.hide
class Resource3(Resource):
def get(self):
return {}

# Hide methods
@api.route('/resource4/')
@api.doc(delete=False)
class Resource4(Resource):
def get(self):
return {}

@api.doc(False)
def post(self):
return {}

@api.hide
def put(self):
return {}

def delete(self):
return {}

没有附加资源的命名空间标签将自动从文档中隐藏。

记录授权

您可以使用authorizations关键字参数来记录授权信息。有关配置详细信息,请参阅Swagger 身份验证文档

  • authorizations是 SwaggersecurityDefinitions配置的 Python 字典表示。
1
2
3
4
5
6
7
8
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
}
}
api = Api(app, authorizations=authorizations)

然后装饰每个需要授权的资源和方法:

1
2
3
4
5
6
7
8
9
@api.route('/resource/')
class Resource1(Resource):
@api.doc(security='apikey')
def get(self):
pass

@api.doc(security='apikey')
def post(self):
pass

security您可以使用构造函数上的参数全局应用此要求Api

1
2
3
4
5
6
7
8
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
}
}
api = Api(app, authorizations=authorizations, security='apikey')

您可以有多个安全方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API'
},
'oauth2': {
'type': 'oauth2',
'flow': 'accessCode',
'tokenUrl': 'https://somewhere.com/token',
'authorizationUrl': 'https://somewhere.com/auth',
'scopes': {
'read': 'Grant read-only access',
'write': 'Grant read-write access',
}
}
}
api = Api(self.app, security=['apikey', {'oauth2': 'read'}], authorizations=authorizations)

可以为特定方法覆盖安全方案:

1
2
3
4
5
@api.route('/authorizations/')
class Authorized(Resource):
@api.doc(security=[{'oauth2': ['read', 'write']}])
def get(self):
return {}

None您可以通过传递或空列表作为security参数来禁用给定资源或方法的安全性:

1
2
3
4
5
6
7
8
9
@api.route('/without-authorization/')
class WithoutAuthorization(Resource):
@api.doc(security=[])
def get(self):
return {}

@api.doc(security=None)
def post(self):
return {}

公开供应商扩展

Swaggers 允许您公开自定义供应商扩展,您可以在 Flask-RESTPlus 中使用它们和@api.vendor装饰器。

它支持dict或kwargs两种扩展,并执行自动x-前缀:

1
2
3
4
5
6
7
8
9
@api.route('/vendor/')
@api.vendor(extension1='any authorized value')
class Vendor(Resource):
@api.vendor({
'extension-1': {'works': 'with complex values'},
'x-extension-3': 'x- prefix is optionnal',
})
def get(self):
return {}

导出 Swagger 规范

您可以为您的 API 导出 Swagger 规范:

1
2
3
4
5
from flask import json

from myapp import api

print(json.dumps(api.__schema__))

swagger 用户界面

默认flask-restplus提供 Swagger UI 文档,从 API 的根 URL 提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask
from flask_restplus import Api, Resource, fields

app = Flask(__name__)
api = Api(app, version='1.0', title='Sample API',
description='A sample API',
)

@api.route('/my-resource/<id>')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}

@api.response(403, 'Not Authorized')
def post(self, id):
api.abort(403)


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

如果您运行下面的代码并访问 API 的根 URL ( http://localhost:5000 ),您可以查看自动生成的 Swagger UI 文档。

定制

您可以使用参数控制 Swagger UI 路径doc(默认为 API 根目录):

1
2
3
4
5
6
7
8
9
10
from flask import Flask, Blueprint
from flask_restplus import Api

app = Flask(__name__)
blueprint = Blueprint('api', __name__, url_prefix='/api')
api = Api(blueprint, doc='/doc/')

app.register_blueprint(blueprint)

assert url_for('api.doc') == '/api/doc/'

您可以通过设置指定自定义验证器 URL config.SWAGGER_VALIDATOR_URL

1
2
3
4
5
6
7
from flask import Flask
from flask_restplus import Api

app = Flask(__name__)
app.config.SWAGGER_VALIDATOR_URL = 'http://domain.com/validator'

api = Api(app)

您可以启用 [OAuth2 隐式流程]( https://oauth.net/2/grant-types/implicit/ ) 以检索授权令牌,以便在 Swagger UI 中以交互方式测试 api 端点。config.SWAGGER_UI_OAUTH_CLIENT_ID和将 特定于您的 OAuth2 IDP 配置authorizationUrlscopes领域字符串作为查询参数添加到 authorizationUrl 和 tokenUrl。这些价值观都是公共知识。此处未指定_客户端密码。_.. 使用 PKCE 而不是隐式流取决于https://github.com/swagger-api/swagger-ui/issues/5348

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask
app = Flask(__name__)

app.config.SWAGGER_UI_OAUTH_CLIENT_ID = 'MyClientId'
app.config.SWAGGER_UI_OAUTH_REALM = '-'
app.config.SWAGGER_UI_OAUTH_APP_NAME = 'Demo'

api = Api(
app,
title=app.config.SWAGGER_UI_OAUTH_APP_NAME,
security={'OAuth2': ['read', 'write']},
authorizations={
'OAuth2': {
'type': 'oauth2',
'flow': 'implicit',
'authorizationUrl': 'https://idp.example.com/authorize?audience=https://app.example.com',
'clientId': app.config.SWAGGER_UI_OAUTH_CLIENT_ID,
'scopes': {
'openid': 'Get ID token',
'profile': 'Get identity',
}
}
}
)

您还可以使用config.SWAGGER_UI_DOC_EXPANSION 设置('none''list')指定初始扩展状态'full'

1
2
3
4
5
6
7
from flask import Flask
from flask_restplus import Api

app = Flask(__name__)
app.config.SWAGGER_UI_DOC_EXPANSION = 'list'

api = Api(app)

默认情况下,操作 ID 和请求持续时间是隐藏的,您可以分别启用它们:

1
2
3
4
5
6
7
8
from flask import Flask
from flask_restplus import Api

app = Flask(__name__)
app.config.SWAGGER_UI_OPERATION_ID = True
app.config.SWAGGER_UI_REQUEST_DURATION = True

api = Api(app)

如果需要自定义 UI,可以使用documentation()装饰器注册自定义视图功能:

1
2
3
4
5
6
7
8
9
from flask import Flask
from flask_restplus import Api, apidoc

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

@api.documentation
def custom_ui():
return apidoc.ui_for(api)

配置“试用”

默认情况下,所有路径和方法都有一个“试用”按钮,用于在浏览器中执行 API 请求。这些可以通过配置选项按方法禁用,支持与Swagger UI 参数SWAGGER_SUPPORTED_SUBMIT_METHODS相同的值。supportedSubmitMethods

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask
from flask_restplus import Api

app = Flask(__name__)

# disable Try it Out for all methods
app.config.SWAGGER_SUPPORTED_SUBMIT_METHODS = []

# enable Try it Out for specific methods
app.config.SWAGGER_SUPPORTED_SUBMIT_METHODS = ["get", "post"]

api = Api(app)

禁用文档

要完全禁用 Swagger UI,请设置doc=False

from flask import Flask
from flask_restplus import Api

app = Flask(__name__)
api = Api(app, doc=False)

flask restplus swagger
https://flepeng.github.io/021-Python-32-框架-Flask-flask-restplus-flask-restplus-swagger/
作者
Lepeng
发布于
2021年3月31日
许可协议