Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple versions swagger #141

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions flask_apispec/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@ def get_pet(pet_id):

:param Flask app: App associated with API documentation
:param APISpec spec: apispec specification associated with API documentation
:param bool support_multiple_version: support multiple version swaggers
by register doc to 'swagger-{api_version}' url
"""

def __init__(self, app=None):
def __init__(self, app=None, support_multiple_version=False):
self._deferred = []
self.app = app
self.view_converter = None
self.resource_converter = None
self.spec = None
self.support_multiple_version = support_multiple_version

if app:
self.init_app(app)
Expand All @@ -67,9 +70,15 @@ def _defer(self, callable, *args, **kwargs):
if self.app:
bound()

@property
def blueprint_name(self):
if self.support_multiple_version:
return 'flask-apispec-' + self.spec.version
return 'flask-apispec'

def add_swagger_routes(self):
blueprint = flask.Blueprint(
'flask-apispec',
self.blueprint_name,
__name__,
static_folder='./static',
template_folder='./templates',
Expand All @@ -78,19 +87,37 @@ def add_swagger_routes(self):

json_url = self.app.config.get('APISPEC_SWAGGER_URL', '/swagger/')
if json_url:
if self.support_multiple_version:
json_url = self.make_url_with_suffix_version(json_url)

blueprint.add_url_rule(json_url, 'swagger-json', self.swagger_json)

ui_url = self.app.config.get('APISPEC_SWAGGER_UI_URL', '/swagger-ui/')
if ui_url:
if self.support_multiple_version:
ui_url = self.make_url_with_suffix_version(ui_url)
blueprint.add_url_rule(ui_url, 'swagger-ui', self.swagger_ui)

self.app.register_blueprint(blueprint)

def make_url_with_suffix_version(self, url):
# adding version suffix
if url.endswith('/'):
url = url[:-1] + '-' + self.spec.version + '/'
elif url.endswith('.json') or url.endswith('.html'):
# support extension url
url = url.replace('.json', '-' + self.spec.version + '.json') \
.replace('.html', '-' + self.spec.version + '.html')
else:
url += '-' + self.spec.version
return url

def swagger_json(self):
return flask.jsonify(self.spec.to_dict())

def swagger_ui(self):
return flask.render_template('swagger-ui.html')
return flask.render_template('swagger-ui.html',
blueprint_name=self.blueprint_name)

def register_existing_resources(self):
for name, rule in self.app.view_functions.items():
Expand Down
16 changes: 8 additions & 8 deletions flask_apispec/templates/swagger-ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="icon" type="image/png" href="{{ url_for('flask-apispec.static', filename='favicon-32x32.png') }}" sizes="32x32" />
<link rel="icon" type="image/png" href="{{ url_for('flask-apispec.static', filename='favicon-16x16.png') }}" sizes="16x16" />
<link href="{{ url_for('flask-apispec.static', filename='swagger-ui.css') }}" rel="stylesheet" type="text/css"/>
<link rel="icon" type="image/png" href="{{ url_for(blueprint_name + '.static', filename='favicon-32x32.png') }}" sizes="32x32" />
<link rel="icon" type="image/png" href="{{ url_for(blueprint_name + '.static', filename='favicon-16x16.png') }}" sizes="16x16" />
<link href="{{ url_for(blueprint_name + '.static', filename='swagger-ui.css') }}" rel="stylesheet" type="text/css"/>
</head>

<body class="swagger-section">
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
<script src="{{ url_for('flask-apispec.static', filename='swagger-ui-bundle.js') }}" type="text/javascript"></script>
<script src="{{ url_for('flask-apispec.static', filename='swagger-ui-standalone-preset.js') }}" type="text/javascript"></script>
<script src="{{ url_for(blueprint_name + '.static', filename='swagger-ui-bundle.js') }}" type="text/javascript"></script>
<script src="{{ url_for(blueprint_name + '.static', filename='swagger-ui-standalone-preset.js') }}" type="text/javascript"></script>
<script type="text/javascript">
var ui = SwaggerUIBundle({
url: "{{ url_for('flask-apispec.swagger-json') }}",
url: "{{ url_for(blueprint_name + '.swagger-json') }}",
dom_id: '#swagger-ui-container',
deepLinking: true,
presets: [
Expand All @@ -26,8 +26,8 @@
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "BaseLayout"
})
window.ui = ui
});
window.ui = ui;
</script>
</body>

Expand Down
27 changes: 27 additions & 0 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import random

import pytest
from flask import Blueprint
Expand Down Expand Up @@ -98,3 +99,29 @@ def test_apispec_config(self, app):
assert docs.spec.title == 'test-extension'
assert docs.spec.version == '2.1'
assert docs.spec.openapi_version == '2.0'

def test_make_url_with_suffix_version_with_version(self, app):
app.config['APISPEC_VERSION'] = 'v2'
docs = FlaskApiSpec(app, support_multiple_version=True)

assert docs.make_url_with_suffix_version('/swagger/') == '/swagger-v2/'
assert docs.make_url_with_suffix_version('/swagger-ui/') == '/swagger-ui-v2/'
assert docs.make_url_with_suffix_version('/swagger.json') == '/swagger-v2.json'
assert docs.make_url_with_suffix_version('/swagger.html') == '/swagger-v2.html'
assert docs.make_url_with_suffix_version('/swagger') == '/swagger-v2'

def test_urls_with_support_multiple_version(self, app, client):
app.config['APISPEC_VERSION'] = 'v2'
docs = FlaskApiSpec(app, support_multiple_version=True)
res = client.get('/swagger-v2/')
assert res.json == docs.spec.to_dict()
client.get('/swagger-ui-v2/')

def test_support_multiple_version_by_changing_blueprint_name(self, app):
version = 'v{}'.format(random.randint(1, 5))
app.config['APISPEC_VERSION'] = version
docs = FlaskApiSpec(app, support_multiple_version=True)
assert docs.blueprint_name == 'flask-apispec-' + version

docs_without_support_multiple_version = FlaskApiSpec(app)
assert docs_without_support_multiple_version.blueprint_name == 'flask-apispec'
2 changes: 1 addition & 1 deletion tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def path(self, app, spec, function_view):
return spec._paths['/bands/{band_id}/']

def test_responses(self, schemas, path):
response = path['delete']['responses'][204]
response = path['delete']['responses']['204']
assert response['description'] == 'a deleted band'
assert response['schema'] == {}

Expand Down
9 changes: 8 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,15 @@ def test_schemas_none(self, app, client, models, schemas):
class ConcreteResource(MethodResource):
@marshal_with(None, code=204)
def delete(self, **kwargs):
return make_response('', 204)
from flask import Response
response = Response(None, 204)

# remove content-type as webtest raises error for this
# https://github.com/Pylons/webtest/blob/master/webtest/lint.py#L543
response.headers.pop('Content-Type', None)
return response

app.add_url_rule('/<id>/', view_func=ConcreteResource.as_view('concrete'))
res = client.delete('/5/')
assert res.status_code == 204
assert res.body == b''