Skip to content

Commit

Permalink
Add swagger_ui_config option to pass config to the Swagger UI (#948)
Browse files Browse the repository at this point in the history
* Add swagger_ui_config option to configure the Swagger UI

There was no possibility to configure the Swagger UI. Through
this new option a config object can be passed to the UI via
a `swagger-ui-config.json` file.

* Add tests for swagger_ui_config option
  • Loading branch information
christiansiegel authored and hjacobs committed Nov 5, 2019
1 parent 4a1c69e commit 67f48ae
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 10 deletions.
23 changes: 21 additions & 2 deletions connexion/apis/aiohttp_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ def add_swagger_ui(self):
self._get_swagger_ui_home
)

if self.options.openapi_console_ui_config is not None:
self.subapp.router.add_route(
'GET',
console_ui_path + '/swagger-ui-config.json',
self._get_swagger_ui_config
)

# we have to add an explicit redirect instead of relying on the
# normalize_path_middleware because we also serve static files
# from this dir (below)
Expand Down Expand Up @@ -212,8 +219,20 @@ def redirect(request):
@aiohttp_jinja2.template('index.j2')
@asyncio.coroutine
def _get_swagger_ui_home(self, req):
return {'openapi_spec_url': (self.base_path +
self.options.openapi_spec_path)}
template_variables = {
'openapi_spec_url': (self.base_path + self.options.openapi_spec_path)
}
if self.options.openapi_console_ui_config is not None:
template_variables['configUrl'] = 'swagger-ui-config.json'
return template_variables

@asyncio.coroutine
def _get_swagger_ui_config(self, req):
return web.Response(
status=200,
content_type='text/json',
body=self.jsonifier.dumps(self.options.openapi_console_ui_config)
)

def add_auth_on_not_found(self, security, security_definitions):
"""
Expand Down
23 changes: 17 additions & 6 deletions connexion/apis/flask_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ def add_swagger_ui(self):
self.base_path,
console_ui_path)

if self.options.openapi_console_ui_config is not None:
config_endpoint_name = "{name}_swagger_ui_config".format(name=self.blueprint.name)
config_file_url = '/{console_ui_path}/swagger-ui-config.json'.format(
console_ui_path=console_ui_path)

self.blueprint.add_url_rule(config_file_url,
config_endpoint_name,
lambda: flask.jsonify(self.options.openapi_console_ui_config))

static_endpoint_name = "{name}_swagger_ui_static".format(name=self.blueprint.name)
static_files_url = '/{console_ui_path}/<path:filename>'.format(
console_ui_path=console_ui_path)
Expand All @@ -78,8 +87,8 @@ def add_swagger_ui(self):
self._handlers.console_ui_static_files)

index_endpoint_name = "{name}_swagger_ui_index".format(name=self.blueprint.name)
console_ui_url = '/{swagger_url}/'.format(
swagger_url=self.options.openapi_console_ui_path.strip('/'))
console_ui_url = '/{console_ui_path}/'.format(
console_ui_path=console_ui_path)

self.blueprint.add_url_rule(console_ui_url,
index_endpoint_name,
Expand Down Expand Up @@ -299,10 +308,12 @@ def console_ui_home(self):
:return:
"""
return flask.render_template(
'index.j2',
openapi_spec_url=(self.base_path + self.options.openapi_spec_path)
)
template_variables = {
'openapi_spec_url': (self.base_path + self.options.openapi_spec_path)
}
if self.options.openapi_console_ui_config is not None:
template_variables['configUrl'] = 'swagger-ui-config.json'
return flask.render_template('index.j2', **template_variables)

def console_ui_static_files(self, filename):
"""
Expand Down
10 changes: 10 additions & 0 deletions connexion/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ def openapi_console_ui_from_dir(self):
"""
return self._options.get('swagger_path', self.swagger_ui_local_path)

@property
def openapi_console_ui_config(self):
# type: () -> dict
"""
Custom OpenAPI Console UI config.
Default: None
"""
return self._options.get('swagger_ui_config', None)

@property
def uri_parser_class(self):
# type: () -> AbstractURIParser
Expand Down
12 changes: 12 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ You can also disable it at the API level:
app = connexion.FlaskApp(__name__, specification_dir='openapi/')
app.add_api('my_api.yaml', options=options)
You can pass custom Swagger UI `Configuration Parameters`_ like e.g.
`displayOperationId` through the `swagger_ui_config` option:

.. code-block:: python
options = {"swagger_ui_config": {"displayOperationId": True}}
app = connexion.FlaskApp(__name__, specification_dir='openapi/',
options=options)
.. _Configuration Parameters: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#parameters

Server Backend
--------------
By default connexion uses the default flask server but you can also use Tornado_ or gevent_ as the HTTP server, to do so set server
Expand Down
53 changes: 51 additions & 2 deletions tests/aiohttp/test_aiohttp_simple_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,65 @@ def test_swagger_ui(aiohttp_api_spec_dir, aiohttp_client):


@asyncio.coroutine
def test_swagger_ui_index(aiohttp_api_spec_dir, aiohttp_client):
def test_swagger_ui_config_json(aiohttp_api_spec_dir, aiohttp_client):
""" Verify the swagger-ui-config.json file is returned for swagger_ui_config option passed to app. """
swagger_ui_config = {"displayOperationId": True}
options = {"swagger_ui_config": swagger_ui_config}
app = AioHttpApp(__name__, port=5001,
specification_dir=aiohttp_api_spec_dir,
options=options,
debug=True)
api = app.add_api('swagger_simple.yaml')

app_client = yield from aiohttp_client(app.app)
swagger_ui_config_json = yield from app_client.get('/v1.0/ui/swagger-ui-config.json')
json_ = yield from swagger_ui_config_json.read()

assert swagger_ui_config_json.status == 200
assert swagger_ui_config == json.loads(json_)


@asyncio.coroutine
def test_no_swagger_ui_config_json(aiohttp_api_spec_dir, aiohttp_client):
""" Verify the swagger-ui-config.json file is not returned when the swagger_ui_config option not passed to app. """
app = AioHttpApp(__name__, port=5001,
specification_dir=aiohttp_api_spec_dir,
debug=True)
app.add_api('swagger_simple.yaml')

app_client = yield from aiohttp_client(app.app)
swagger_ui_config_json = yield from app_client.get('/v1.0/ui/swagger-ui-config.json')
assert swagger_ui_config_json.status == 404


@asyncio.coroutine
def test_swagger_ui_index(aiohttp_api_spec_dir, aiohttp_client):
app = AioHttpApp(__name__, port=5001,
specification_dir=aiohttp_api_spec_dir,
debug=True)
app.add_api('openapi_secure.yaml')

app_client = yield from aiohttp_client(app.app)
swagger_ui = yield from app_client.get('/v1.0/ui/index.html')
assert swagger_ui.status == 200
assert b'url = "/v1.0/swagger.json"' in (yield from swagger_ui.read())
assert b'url: "/v1.0/openapi.json"' in (yield from swagger_ui.read())
assert b'swagger-ui-config.json' not in (yield from swagger_ui.read())


@asyncio.coroutine
def test_swagger_ui_index_with_config(aiohttp_api_spec_dir, aiohttp_client):
swagger_ui_config = {"displayOperationId": True}
options = {"swagger_ui_config": swagger_ui_config}
app = AioHttpApp(__name__, port=5001,
specification_dir=aiohttp_api_spec_dir,
options=options,
debug=True)
app.add_api('openapi_secure.yaml')

app_client = yield from aiohttp_client(app.app)
swagger_ui = yield from app_client.get('/v1.0/ui/index.html')
assert swagger_ui.status == 200
assert b'configUrl: "swagger-ui-config.json"' in (yield from swagger_ui.read())


@asyncio.coroutine
Expand Down
53 changes: 53 additions & 0 deletions tests/api/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ def test_app_with_different_uri_parser(simple_api_spec_dir):
assert j == ['a', 'b', 'c']


@pytest.mark.parametrize("spec", SPECS)
def test_swagger_ui(simple_api_spec_dir, spec):
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
app.add_api(spec)
app_client = app.app.test_client()
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
assert swagger_ui.status_code == 200
spec_json_filename = '/v1.0/{spec}'.format(spec=spec.replace("yaml", "json"))
assert spec_json_filename.encode() in swagger_ui.data
if "openapi" in spec:
assert b'swagger-ui-config.json' not in swagger_ui.data


@pytest.mark.parametrize("spec", SPECS)
def test_swagger_ui_with_config(simple_api_spec_dir, spec):
swagger_ui_config = {"displayOperationId": True}
options = {"swagger_ui_config": swagger_ui_config}
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir,
options=options, debug=True)
app.add_api(spec)
app_client = app.app.test_client()
swagger_ui = app_client.get('/v1.0/ui/') # type: flask.Response
assert swagger_ui.status_code == 200
if "openapi" in spec:
assert b'configUrl: "swagger-ui-config.json"' in swagger_ui.data


@pytest.mark.parametrize("spec", SPECS)
def test_no_swagger_ui(simple_api_spec_dir, spec):
options = {"swagger_ui": False}
Expand All @@ -89,6 +116,32 @@ def test_no_swagger_ui(simple_api_spec_dir, spec):
assert swagger_ui2.status_code == 404


@pytest.mark.parametrize("spec", SPECS)
def test_swagger_ui_config_json(simple_api_spec_dir, spec):
""" Verify the swagger-ui-config.json file is returned for swagger_ui_config option passed to app. """
swagger_ui_config = {"displayOperationId": True}
options = {"swagger_ui_config": swagger_ui_config}
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir,
options=options, debug=True)
app.add_api(spec)
app_client = app.app.test_client()
url = '/v1.0/ui/swagger-ui-config.json'
swagger_ui_config_json = app_client.get(url) # type: flask.Response
assert swagger_ui_config_json.status_code == 200
assert swagger_ui_config == json.loads(swagger_ui_config_json.get_data(as_text=True))


@pytest.mark.parametrize("spec", SPECS)
def test_no_swagger_ui_config_json(simple_api_spec_dir, spec):
""" Verify the swagger-ui-config.json file is not returned when the swagger_ui_config option not passed to app. """
app = App(__name__, port=5001, specification_dir=simple_api_spec_dir, debug=True)
app.add_api(spec)
app_client = app.app.test_client()
url = '/v1.0/ui/swagger-ui-config.json'
swagger_ui_config_json = app_client.get(url) # type: flask.Response
assert swagger_ui_config_json.status_code == 404


@pytest.mark.parametrize("spec", SPECS)
def test_swagger_json_app(simple_api_spec_dir, spec):
""" Verify the spec json file is returned for default setting passed to app. """
Expand Down

0 comments on commit 67f48ae

Please sign in to comment.