From 415d383740bffb772cee994a441de8667f96537c Mon Sep 17 00:00:00 2001 From: Robbe Sneyders Date: Fri, 13 Oct 2023 20:33:22 +0200 Subject: [PATCH] Add swagger-ui docs and clean up swagger-ui options (#1739) Contributes to #1531 --- connexion/apps/abstract.py | 9 +- connexion/apps/asynchronous.py | 7 +- connexion/apps/flask.py | 7 +- connexion/cli.py | 6 +- connexion/middleware/main.py | 11 +- connexion/middleware/swagger_ui.py | 22 ++-- connexion/options.py | 175 +++++++++++------------------ docs/index.rst | 3 +- docs/quickstart.rst | 4 +- docs/swagger_ui.rst | 71 ++++++++++++ docs/v3.rst | 4 +- tests/api/test_bootstrap.py | 13 ++- tests/test_cli.py | 6 +- 13 files changed, 189 insertions(+), 149 deletions(-) create mode 100644 docs/swagger_ui.rst diff --git a/connexion/apps/abstract.py b/connexion/apps/abstract.py index 296d32afd..63bcf842a 100644 --- a/connexion/apps/abstract.py +++ b/connexion/apps/abstract.py @@ -12,6 +12,7 @@ from connexion.jsonifier import Jsonifier from connexion.middleware import ConnexionMiddleware, MiddlewarePosition, SpecMiddleware from connexion.middleware.lifespan import Lifespan +from connexion.options import SwaggerUIOptions from connexion.resolver import Resolver from connexion.uri_parsing import AbstractURIParser @@ -43,7 +44,7 @@ def __init__( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, @@ -72,8 +73,8 @@ def __init__( start. :param strict_validation: When True, extra form or query parameters not defined in the specification result in a validation error. Defaults to False. - :param swagger_ui_options: A dict with configuration options for the swagger ui. See - :class:`options.ConnexionOptions`. + :param swagger_ui_options: Instance of :class:`options.ConnexionOptions` with + configuration options for the swagger ui. :param uri_parser_class: Class to use for uri parsing. See :mod:`uri_parsing`. :param validate_responses: Whether to validate responses against the specification. This has an impact on performance. Defaults to False. @@ -128,7 +129,7 @@ def add_api( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, diff --git a/connexion/apps/asynchronous.py b/connexion/apps/asynchronous.py index a0702c0f5..1d0e3fc96 100644 --- a/connexion/apps/asynchronous.py +++ b/connexion/apps/asynchronous.py @@ -17,6 +17,7 @@ from connexion.middleware.abstract import RoutedAPI, RoutedMiddleware from connexion.middleware.lifespan import Lifespan from connexion.operations import AbstractOperation +from connexion.options import SwaggerUIOptions from connexion.resolver import Resolver from connexion.uri_parsing import AbstractURIParser @@ -131,7 +132,7 @@ def __init__( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, @@ -161,8 +162,8 @@ def __init__( start. :param strict_validation: When True, extra form or query parameters not defined in the specification result in a validation error. Defaults to False. - :param swagger_ui_options: A dict with configuration options for the swagger ui. See - :class:`options.ConnexionOptions`. + :param swagger_ui_options: Instance of :class:`options.ConnexionOptions` with + configuration options for the swagger ui. :param uri_parser_class: Class to use for uri parsing. See :mod:`uri_parsing`. :param validate_responses: Whether to validate responses against the specification. This has an impact on performance. Defaults to False. diff --git a/connexion/apps/flask.py b/connexion/apps/flask.py index 5782218b1..2121348b4 100644 --- a/connexion/apps/flask.py +++ b/connexion/apps/flask.py @@ -20,6 +20,7 @@ from connexion.middleware.abstract import AbstractRoutingAPI, SpecMiddleware from connexion.middleware.lifespan import Lifespan from connexion.operations import AbstractOperation +from connexion.options import SwaggerUIOptions from connexion.problem import problem from connexion.resolver import Resolver from connexion.uri_parsing import AbstractURIParser @@ -188,7 +189,7 @@ def __init__( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, @@ -221,8 +222,8 @@ def __init__( start. :param strict_validation: When True, extra form or query parameters not defined in the specification result in a validation error. Defaults to False. - :param swagger_ui_options: A dict with configuration options for the swagger ui. See - :class:`options.ConnexionOptions`. + :param swagger_ui_options: Instance of :class:`options.ConnexionOptions` with + configuration options for the swagger ui. :param uri_parser_class: Class to use for uri parsing. See :mod:`uri_parsing`. :param validate_responses: Whether to validate responses against the specification. This has an impact on performance. Defaults to False. diff --git a/connexion/cli.py b/connexion/cli.py index f3f75c3bb..66be77c30 100644 --- a/connexion/cli.py +++ b/connexion/cli.py @@ -8,12 +8,16 @@ from os import path import click -import importlib_metadata from clickclick import AliasedGroup import connexion from connexion.mock import MockResolver +try: + import importlib_metadata +except ImportError: + import importlib.metadata as importlib_metadata # type: ignore + logger = logging.getLogger("connexion.cli") FLASK_APP = "flask" diff --git a/connexion/middleware/main.py b/connexion/middleware/main.py index 4b5d84225..1d775f817 100644 --- a/connexion/middleware/main.py +++ b/connexion/middleware/main.py @@ -21,6 +21,7 @@ from connexion.middleware.routing import RoutingMiddleware from connexion.middleware.security import SecurityMiddleware from connexion.middleware.swagger_ui import SwaggerUIMiddleware +from connexion.options import SwaggerUIOptions from connexion.resolver import Resolver from connexion.uri_parsing import AbstractURIParser from connexion.utils import inspect_function_arguments @@ -51,7 +52,7 @@ class _Options: resolver_error: t.Optional[int] = None resolver_error_handler: t.Optional[t.Callable] = field(init=False) strict_validation: t.Optional[bool] = False - swagger_ui_options: t.Optional[dict] = None + swagger_ui_options: t.Optional[SwaggerUIOptions] = None uri_parser_class: t.Optional[AbstractURIParser] = None validate_responses: t.Optional[bool] = False validator_map: t.Optional[dict] = None @@ -186,7 +187,7 @@ def __init__( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, @@ -214,8 +215,8 @@ def __init__( start. :param strict_validation: When True, extra form or query parameters not defined in the specification result in a validation error. Defaults to False. - :param swagger_ui_options: A dict with configuration options for the swagger ui. See - :class:`options.ConnexionOptions`. + :param swagger_ui_options: Instance of :class:`options.ConnexionOptions` with + configuration options for the swagger ui. :param uri_parser_class: Class to use for uri parsing. See :mod:`uri_parsing`. :param validate_responses: Whether to validate responses against the specification. This has an impact on performance. Defaults to False. @@ -338,7 +339,7 @@ def add_api( resolver: t.Optional[t.Union[Resolver, t.Callable]] = None, resolver_error: t.Optional[int] = None, strict_validation: t.Optional[bool] = None, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, uri_parser_class: t.Optional[AbstractURIParser] = None, validate_responses: t.Optional[bool] = None, validator_map: t.Optional[dict] = None, diff --git a/connexion/middleware/swagger_ui.py b/connexion/middleware/swagger_ui.py index b157e9b2a..61d43ac36 100644 --- a/connexion/middleware/swagger_ui.py +++ b/connexion/middleware/swagger_ui.py @@ -16,7 +16,7 @@ from connexion.jsonifier import Jsonifier from connexion.middleware import SpecMiddleware from connexion.middleware.abstract import AbstractSpecAPI -from connexion.options import SwaggerUIOptions +from connexion.options import SwaggerUIConfig, SwaggerUIOptions from connexion.utils import yamldumper logger = logging.getLogger("connexion.middleware.swagger_ui") @@ -30,13 +30,13 @@ def __init__( self, *args, default: ASGIApp, - swagger_ui_options: t.Optional[dict] = None, + swagger_ui_options: t.Optional[SwaggerUIOptions] = None, **kwargs ): super().__init__(*args, **kwargs) self.router = Router(default=default) - self.options = SwaggerUIOptions( + self.options = SwaggerUIConfig( swagger_ui_options, oas_version=self.specification.version ) @@ -44,11 +44,11 @@ def __init__( self.add_openapi_json() self.add_openapi_yaml() - if self.options.openapi_console_ui_available: + if self.options.swagger_ui_available: self.add_swagger_ui() self._templates = Jinja2Templates( - directory=str(self.options.openapi_console_ui_from_dir) + directory=str(self.options.swagger_ui_template_dir) ) @staticmethod @@ -121,7 +121,7 @@ def add_swagger_ui(self): """ Adds swagger ui to {base_path}/ui/ """ - console_ui_path = self.options.openapi_console_ui_path.strip().rstrip("/") + console_ui_path = self.options.swagger_ui_path.strip().rstrip("/") logger.debug("Adding swagger-ui: %s%s/", self.base_path, console_ui_path) for path in ( @@ -132,7 +132,7 @@ def add_swagger_ui(self): methods=["GET"], path=path, endpoint=self._get_swagger_ui_home ) - if self.options.openapi_console_ui_config is not None: + if self.options.swagger_ui_config: self.router.add_route( methods=["GET"], path=console_ui_path + "/swagger-ui-config.json", @@ -155,7 +155,7 @@ async def redirect(request): # serve index.html, so we add the redirect above. self.router.mount( path=console_ui_path, - app=StaticFiles(directory=str(self.options.openapi_console_ui_from_dir)), + app=StaticFiles(directory=str(self.options.swagger_ui_template_dir)), name="swagger_ui_static", ) @@ -164,9 +164,9 @@ async def _get_swagger_ui_home(self, req): template_variables = { "request": req, "openapi_spec_url": (base_path + self.options.openapi_spec_path), - **self.options.openapi_console_ui_index_template_variables, + **self.options.swagger_ui_template_arguments, } - if self.options.openapi_console_ui_config is not None: + if self.options.swagger_ui_config: template_variables["configUrl"] = "swagger-ui-config.json" return self._templates.TemplateResponse("index.j2", template_variables) @@ -175,7 +175,7 @@ async def _get_swagger_ui_config(self, request): return StarletteResponse( status_code=200, media_type="application/json", - content=json.dumps(self.options.openapi_console_ui_config), + content=json.dumps(self.options.swagger_ui_config), ) diff --git a/connexion/options.py b/connexion/options.py index 7f9279d75..1abf8ec14 100644 --- a/connexion/options.py +++ b/connexion/options.py @@ -1,14 +1,14 @@ """ This module defines a Connexion specific options class to pass to the Connexion App or API. """ - +import dataclasses import logging -from typing import Optional # NOQA +import typing as t try: - from py_swagger_ui import swagger_ui_path + from py_swagger_ui import swagger_ui_path as default_template_dir except ImportError: - swagger_ui_path = None + default_template_dir = None NO_UI_MSG = """The swagger_ui directory could not be found. Please install connexion with extra install: pip install connexion[swagger-ui] @@ -18,131 +18,82 @@ logger = logging.getLogger("connexion.options") +@dataclasses.dataclass class SwaggerUIOptions: - """Class holding swagger UI specific options.""" + """Options to configure the Swagger UI. + + :param serve_spec: Whether to serve the Swagger / OpenAPI Specification + :param spec_path: Where to serve the Swagger / OpenAPI Specification + + :param swagger_ui: Whether to serve the Swagger UI + :param swagger_ui_path: Where to serve the Swagger UI + :param swagger_ui_config: Options to configure the Swagger UI. See + https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration + for an overview of the available options. + :param swagger_ui_template_dir: Directory with static files to use to serve Swagger UI + :param swagger_ui_template_arguments: Arguments passed to the Swagger UI template. Useful + when providing your own template dir with additional template arguments. + """ - def __init__(self, options=None, oas_version=(2,)): - self._options = {} - self.oas_version = oas_version - self.swagger_ui_local_path = swagger_ui_path - if self.oas_version >= (3, 0, 0): - self.openapi_spec_name = "/openapi.json" - else: - self.openapi_spec_name = "/swagger.json" + serve_spec: bool = True + spec_path: t.Optional[str] = None - if options: - self._options.update(filter_values(options)) + swagger_ui: bool = True + swagger_ui_config: dict = dataclasses.field(default_factory=dict) + swagger_ui_path: str = "/ui" + swagger_ui_template_dir: t.Optional[str] = None + swagger_ui_template_arguments: dict = dataclasses.field(default_factory=dict) - def extend(self, new_values=None): - # type: (Optional[dict]) -> SwaggerUIOptions - """ - Return a new instance of `ConnexionOptions` using as default the currently - defined options. - """ - if new_values is None: - new_values = {} - options = dict(self._options) - options.update(filter_values(new_values)) - return SwaggerUIOptions(options, self.oas_version) +class SwaggerUIConfig: + """Class holding swagger UI specific options.""" - def as_dict(self): - return self._options + def __init__( + self, + options: t.Optional[SwaggerUIOptions] = None, + oas_version: t.Tuple[int, ...] = (2,), + ): + if oas_version >= (3, 0, 0): + self.spec_path = "/openapi.json" + else: + self.spec_path = "/swagger.json" - @property - def openapi_spec_available(self): - # type: () -> bool - """ - Whether to make available the OpenAPI Specification under - `openapi_spec_path`. - - Default: True - """ - deprecated_option = self._options.get("swagger_json", True) - serve_spec = self._options.get("serve_spec", deprecated_option) - if "swagger_json" in self._options: - deprecation_warning = ( - "The 'swagger_json' option is deprecated. " - "Please use 'serve_spec' instead" - ) - logger.warning(deprecation_warning) - return serve_spec + self._options = options or SwaggerUIOptions() @property - def openapi_console_ui_available(self): - # type: () -> bool - """ - Whether to make the OpenAPI Console UI available under the path - defined in `openapi_console_ui_path` option. - - Default: True - """ - if ( - self._options.get("swagger_ui", True) - and self.openapi_console_ui_from_dir is None - ): - logger.warning(NO_UI_MSG) - return False - return self._options.get("swagger_ui", True) + def openapi_spec_available(self) -> bool: + """Whether to make the OpenAPI Specification available.""" + return self._options.serve_spec @property - def openapi_spec_path(self): - # type: () -> str - """ - Path to mount the OpenAPI Console UI and make it accessible via a browser. - - Default: /openapi.json for openapi3, otherwise /swagger.json - """ - return self._options.get("openapi_spec_path", self.openapi_spec_name) + def openapi_spec_path(self) -> str: + """Path to host the Swagger UI.""" + return self._options.spec_path or self.spec_path @property - def openapi_console_ui_path(self): - # type: () -> str - """ - Path to mount the OpenAPI Console UI and make it accessible via a browser. - - Default: /ui - """ - return self._options.get("swagger_url", "/ui") + def swagger_ui_available(self) -> bool: + """Whether to make the Swagger UI available.""" + if self._options.swagger_ui and self.swagger_ui_template_dir is None: + logger.warning(NO_UI_MSG) + return False + return self._options.swagger_ui @property - def openapi_console_ui_from_dir(self): - # type: () -> str - """ - Custom OpenAPI Console UI directory from where Connexion will serve - the static files. - - Default: Connexion's vendored version of the OpenAPI Console UI. - """ - return self._options.get("swagger_path", self.swagger_ui_local_path) + def swagger_ui_path(self) -> str: + """Path to mount the Swagger UI and make it accessible via a browser.""" + return self._options.swagger_ui_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) + def swagger_ui_template_dir(self) -> str: + """Directory with static files to use to serve Swagger UI.""" + return self._options.swagger_ui_template_dir or default_template_dir @property - def openapi_console_ui_index_template_variables(self): - # type: () -> dict - """ - Custom variables passed to the OpenAPI Console UI template. - - Default: {} - """ - return self._options.get("swagger_ui_template_arguments", {}) + def swagger_ui_config(self) -> dict: + """Options to configure the Swagger UI.""" + return self._options.swagger_ui_config - -def filter_values(dictionary): - # type: (dict) -> dict - """ - Remove `None` value entries in the dictionary. - - :param dictionary: - :return: - """ - return {key: value for key, value in dictionary.items() if value is not None} + @property + def swagger_ui_template_arguments(self) -> dict: + """Arguments passed to the Swagger UI template.""" + return self._options.swagger_ui_template_arguments diff --git a/docs/index.rst b/docs/index.rst index 3ba31eb1d..f7dfa86be 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -64,8 +64,9 @@ Documentation quickstart middleware - cli routing + swagger_ui + cli request response security diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5616ed586..87a8d8098 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -236,10 +236,12 @@ If you installed connexion using the :code:`swagger-ui` extra, a Swagger UI is a API, providing interactive documentation. By default the UI is hosted at :code:`{base_path}/ui/` where :code:`base_path`` is the base path of the API. -**https://localhost:{port}/{base_path}/ui/** +**https://{host}/{base_path}/ui/** .. image:: images/swagger_ui.png +Check :doc:`swagger_ui` for information on how to configure the UI. + Full App class reference ------------------------ diff --git a/docs/swagger_ui.rst b/docs/swagger_ui.rst new file mode 100644 index 000000000..aaaa565c5 --- /dev/null +++ b/docs/swagger_ui.rst @@ -0,0 +1,71 @@ +The Swagger UI +============== + +If you installed connexion using the :code:`swagger-ui` extra, a Swagger UI is available for each +API, providing interactive documentation. By default the UI is hosted at :code:`{base_path}/ui/` +where :code:`base_path`` is the base path of the API. + +**https://{host}/{base_path}/ui/** + +.. image:: images/swagger_ui.png + +Configuring the Swagger UI +-------------------------- + +You can change this path through the ``swagger_ui_options`` argument, either whe instantiating +your application, or when adding your api: + + +.. tab-set:: + + .. tab-item:: AsyncApp + :sync: AsyncApp + + .. code-block:: python + :caption: **app.py** + + from connexion import AsyncApp + from connexion.options import SwaggerUIOptions + + options = SwaggerUIOptions(swagger_ui_path="/docs") + + app = AsyncApp(__name__, swagger_ui_options=options) + app.add_api("openapi.yaml", swagger_ui_options=options) + + .. tab-item:: FlaskApp + :sync: FlaskApp + + .. code-block:: python + :caption: **app.py** + + from connexion import FlaskApp + from connexion.options import SwaggerUIOptions + + options = SwaggerUIOptions(swagger_ui_path="/docs") + + app = FlaskApp(__name__, swagger_ui_options=options) + app.add_api("openapi.yaml", swagger_ui_options=options) + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + .. code-block:: python + :caption: **app.py** + + from asgi_framework import App + from connexion import ConnexionMiddleware + from connexion.options import SwaggerUIOptions + + options = SwaggerUIOptions(swagger_ui_path="/docs") + + app = App(__name__) + app = ConnexionMiddleware(app, swagger_ui_options=options) + app.add_api("openapi.yaml", swagger_ui_options=options): + +For a description of all available options, check the :class:`.SwaggerUIOptions` +class. + +.. dropdown:: View a detailed reference of the :code:`SwaggerUIOptions` class + :icon: eye + + .. autoclass:: connexion.options.SwaggerUIOptions diff --git a/docs/v3.rst b/docs/v3.rst index 4f6f29b2d..2069cefcb 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -105,7 +105,9 @@ should work, connexion comes with ``uvicorn`` as an extra: Smaller breaking changes ------------------------ -* The ``options`` argument has been renamed to ``swagger_ui_options`` +* The ``options`` argument has been renamed to ``swagger_ui_options`` and now takes an instance + of the :class:`.SwaggerUIOptions`. The naming of the options themselves have been changed to + better represent their meaning. * The ``uri_parser_class`` is now passed to the ``App`` or its ``add_api()`` method directly instead of via the ``options`` argument. * The ``jsonifier`` is now passed to the ``App`` or its ``add_api()`` method instead of setting it diff --git a/tests/api/test_bootstrap.py b/tests/api/test_bootstrap.py index 916206313..c0a4d44df 100644 --- a/tests/api/test_bootstrap.py +++ b/tests/api/test_bootstrap.py @@ -8,6 +8,7 @@ from connexion.http_facts import METHODS from connexion.json_schema import ExtendedSafeLoader from connexion.middleware.abstract import AbstractRoutingAPI +from connexion.options import SwaggerUIOptions from conftest import TEST_FOLDER, build_app_from_fixture @@ -57,7 +58,7 @@ def test_swagger_ui(simple_api_spec_dir, spec): def test_swagger_ui_with_config(simple_api_spec_dir, spec): swagger_ui_config = {"displayOperationId": True} - swagger_ui_options = {"swagger_ui_config": swagger_ui_config} + swagger_ui_options = SwaggerUIOptions(swagger_ui_config=swagger_ui_config) app = App( __name__, specification_dir=simple_api_spec_dir, @@ -72,7 +73,7 @@ def test_swagger_ui_with_config(simple_api_spec_dir, spec): def test_no_swagger_ui(simple_api_spec_dir, spec): - swagger_ui_options = {"swagger_ui": False} + swagger_ui_options = SwaggerUIOptions(swagger_ui=False) app = App( __name__, specification_dir=simple_api_spec_dir, @@ -85,7 +86,7 @@ def test_no_swagger_ui(simple_api_spec_dir, spec): assert swagger_ui.status_code == 404 app2 = App(__name__, specification_dir=simple_api_spec_dir) - app2.add_api(spec, swagger_ui_options={"swagger_ui": False}) + app2.add_api(spec, swagger_ui_options=SwaggerUIOptions(swagger_ui=False)) app2_client = app2.test_client() swagger_ui2 = app2_client.get("/v1.0/ui/") assert swagger_ui2.status_code == 404 @@ -94,7 +95,7 @@ def test_no_swagger_ui(simple_api_spec_dir, spec): 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} - swagger_ui_options = {"swagger_ui_config": swagger_ui_config} + swagger_ui_options = SwaggerUIOptions(swagger_ui_config=swagger_ui_config) app = App( __name__, specification_dir=simple_api_spec_dir, @@ -142,7 +143,7 @@ def test_swagger_yaml_app(simple_api_spec_dir, spec): def test_no_swagger_json_app(simple_api_spec_dir, spec): """Verify the spec json file is not returned when set to False when creating app.""" - swagger_ui_options = {"serve_spec": False} + swagger_ui_options = SwaggerUIOptions(serve_spec=False) app = App( __name__, specification_dir=simple_api_spec_dir, @@ -193,7 +194,7 @@ def test_swagger_json_api(simple_api_spec_dir, spec): def test_no_swagger_json_api(simple_api_spec_dir, spec): """Verify the spec json file is not returned when set to False when adding api.""" app = App(__name__, specification_dir=simple_api_spec_dir) - app.add_api(spec, swagger_ui_options={"serve_spec": False}) + app.add_api(spec, swagger_ui_options=SwaggerUIOptions(serve_spec=False)) app_client = app.test_client() url = "/v1.0/{spec}".format(spec=spec.replace("yaml", "json")) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7f17db2f3..98375daa2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,6 @@ import logging from unittest.mock import MagicMock -import importlib_metadata import pytest from click.testing import CliRunner from connexion.cli import main @@ -9,6 +8,11 @@ from conftest import FIXTURES_FOLDER +try: + import importlib_metadata +except ImportError: + import importlib.metadata as importlib_metadata + @pytest.fixture(scope="function") def mock_app_run(app_class, monkeypatch):