diff --git a/connexion/apps/abstract.py b/connexion/apps/abstract.py index b92edd063..b80282d8d 100644 --- a/connexion/apps/abstract.py +++ b/connexion/apps/abstract.py @@ -53,8 +53,9 @@ def __init__( :param import_name: The name of the package or module that this object belongs to. If you are using a single module, __name__ is always the correct value. If you however are using a package, it’s usually recommended to hardcode the name of your package there. + :param lifespan: A lifespan context function, which can be used to perform startup and :param middlewares: The list of middlewares to wrap around the application. Defaults to - :obj:`middleware.main.ConnexionmMiddleware.default_middlewares` + :obj:`middleware.main.ConnexionMiddleware.default_middlewares` :param specification_dir: The directory holding the specification(s). The provided path should either be absolute or relative to the root path of the application. Defaults to the root path. @@ -71,8 +72,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 :class:`options.ConnexionOptions` instance with configuration - options for the swagger ui. + :param swagger_ui_options: A dict with configuration options for the swagger ui. See + :class:`options.ConnexionOptions`. :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/asynchronous.py b/connexion/apps/asynchronous.py index 9735b0ec0..f2f93d0c5 100644 --- a/connexion/apps/asynchronous.py +++ b/connexion/apps/asynchronous.py @@ -141,8 +141,10 @@ def __init__( :param import_name: The name of the package or module that this object belongs to. If you are using a single module, __name__ is always the correct value. If you however are using a package, it’s usually recommended to hardcode the name of your package there. + :param lifespan: A lifespan context function, which can be used to perform startup and + shutdown tasks. :param middlewares: The list of middlewares to wrap around the application. Defaults to - :obj:`middleware.main.ConnexionmMiddleware.default_middlewares` + :obj:`middleware.main.ConnexionMiddleware.default_middlewares` :param specification_dir: The directory holding the specification(s). The provided path should either be absolute or relative to the root path of the application. Defaults to the root path. @@ -159,8 +161,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 :class:`options.ConnexionOptions` instance with configuration - options for the swagger ui. + :param swagger_ui_options: A dict with configuration options for the swagger ui. See + :class:`options.ConnexionOptions`. :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 4b0e94e33..946925676 100644 --- a/connexion/apps/flask.py +++ b/connexion/apps/flask.py @@ -201,7 +201,7 @@ def __init__( :param lifespan: A lifespan context function, which can be used to perform startup and shutdown tasks. :param middlewares: The list of middlewares to wrap around the application. Defaults to - :obj:`middleware.main.ConnexionmMiddleware.default_middlewares` + :obj:`middleware.main.ConnexionMiddleware.default_middlewares` :param server_args: Arguments to pass to the Flask application. :param specification_dir: The directory holding the specification(s). The provided path should either be absolute or relative to the root path of the application. Defaults to @@ -221,6 +221,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 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/middleware/main.py b/connexion/middleware/main.py index 0e9b94e2c..2f4da83e5 100644 --- a/connexion/middleware/main.py +++ b/connexion/middleware/main.py @@ -160,8 +160,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 :class:`options.ConnexionOptions` instance with configuration - options for the swagger ui. + :param swagger_ui_options: A dict with configuration options for the swagger ui. See + :class:`options.ConnexionOptions`. :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. @@ -309,8 +309,8 @@ def add_api( 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 :class:`options.ConnexionOptions` instance with configuration - options for the swagger ui. + :param swagger_ui_options: A dict with configuration options for the swagger ui. See + :class:`options.ConnexionOptions`. :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. @@ -358,6 +358,13 @@ def add_api( def add_error_handler( self, code_or_exception: t.Union[int, t.Type[Exception]], function: t.Callable ) -> None: + """ + Register a callable to handle application errors. + + :param code_or_exception: An exception class or the status code of HTTP exceptions to + handle. + :param function: Callable that will handle exception. + """ if self.middleware_stack is not None: raise RuntimeError( "Cannot add error handler after an application has started" diff --git a/docs/conf.py b/docs/conf.py index eaa410974..245527118 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,21 +27,14 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['autoapi.extension'] - -autoapi_type = 'python' -autoapi_options = ['members', - 'inherited-members', - 'undoc-members', - 'show-inheritance', - 'show-module-summary', - 'special-members', - 'imported-members'] -autoapi_python_class_content = 'both' -autoapi_dirs = [ - '../connexion' +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx_copybutton', + 'sphinx_design', ] +autoclass_content = 'both' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/images/swagger_ui.png b/docs/images/swagger_ui.png new file mode 100644 index 000000000..277d1e236 Binary files /dev/null and b/docs/images/swagger_ui.png differ diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 75e372d71..5616ed586 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,116 +1,280 @@ Quickstart ========== +Installation +------------ -Prerequisites -------------- +You can install connexion using pip: -Python 3.6+ +.. code-block:: bash + + $ pip install connexion -Installing It -------------- +Connexion provides 'extras' with optional dependencies to unlock additional features: -In your command line, type this: +- :code:`flask`: Enables the :code:`FlaskApp` to build applications compatible with the Flask + ecosystem. +- :code:`swagger-ui`: Enables a Swagger UI console for your application. +- :code:`uvicorn`: Enables to run the your application using :code:`app.run()` for + development instead of using an external ASGI server. + +You can install them as follows: .. code-block:: bash - $ pip install connexion[swagger-ui] + $ pip install connexion[] + $ pip install connexion[,]. +Creating your application +------------------------- -Running It ----------- +Connexion can be used either as a standalone application or as a middleware wrapping an existing +ASGI (or WSGI) application written using a different framework. The standalone application can be +built using either the :code:`AsyncApp` or :code:`FlaskApp`. -Put your API YAML inside a folder in the root path of your application (e.g ``openapi/``) and then do +- The :code:`AsyncApp` is a lightweight application with native asynchronous support. Use it if you + are starting a new project and have no specific reason to use one of the other options. +- The :code:`FlaskApp` leverages the `Flask` framework, which is useful if you're migrating from + connexion 2.X or you want to leverage the `Flask` ecosystem. +- The :code:`ConnexionMiddleware` can be wrapped around any existing ASGI or WSGI application. + Use it if you already have an application written in a different framework and want to add + functionality provided by connexion -.. code-block:: python +.. tab-set:: - import connexion + .. tab-item:: AsyncApp + :sync: AsyncApp - app = connexion.FlaskApp(__name__, specification_dir='openapi/') - app.add_api('my_api.yaml') - app.run(port=8080) + .. code-block:: python + from connexion import AsyncApp -Dynamic Rendering of Your Specification ---------------------------------------- + app = AsyncApp(__name__) -Connexion uses Jinja2_ to allow specification parameterization through -`arguments` parameter. You can either define specification arguments -globally for the application in the `connexion.App` constructor, or -for each specific API in the `connexion.App#add_api` method: + .. dropdown:: View a detailed reference of the options accepted by the :code:`AsyncApp` + :icon: eye -.. code-block:: python + .. autoclass:: connexion.AsyncApp + :noindex: - app = connexion.FlaskApp(__name__, specification_dir='openapi/', - arguments={'global': 'global_value'}) - app.add_api('my_api.yaml', arguments={'api_local': 'local_value'}) - app.run(port=8080) + .. tab-item:: FlaskApp + :sync: FlaskApp -When a value is provided both globally and on the API, the API value -will take precedence. + .. note:: + To leverage the :code:`FlaskApp`, make sure you install connexion using the + :code:`flask` extra. -The Swagger UI Console ----------------------- -The Swagger UI for an API is available, by default, in -``{base_path}/ui/`` where ``base_path`` is the base path of the API. + .. code-block:: python -You can disable the Swagger UI at the application level: + from connexion import FlaskApp -.. code-block:: python + app = FlaskApp(__name__) + + .. dropdown:: View a detailed reference of the options accepted by the :code:`FlaskApp` + :icon: eye + + .. autoclass:: connexion.FlaskApp + :noindex: + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + .. code-block:: python + + from asgi_framework import App + from connexion import ConnexionMiddleware + + app = App(__name__) + app = ConnexionMiddleware(app) + + + You can also wrap a WSGI application leveraging the :code:`a2wsgi.WSGIMiddleware`: + + .. code-block:: python + + from wsgi_framework import App + from connexion import ConnexionMiddleware + from a2wsgi import WSGIMiddleware + + wsgi_app = App(__name__) + asgi_app = WSGIMiddleware(wsgi_app) + app = ConnexionMiddleware(app) + + .. dropdown:: View a detailed reference of the options accepted by the + :code:`ConnexionMiddleware` + :icon: eye + + .. autoclass:: connexion.ConnexionMiddleware + :noindex: + +Registering an API +------------------ + +While you can register individual routes on your application, connexion really shines when you +register an API defined by an OpenAPI (or Swagger) specification. + +.. grid:: + :padding: 0 + + .. grid-item:: **run.py** + + .. code-block:: python + + def post_greeting(name: str): + return f"Hello {name}", 200 + + app.add_api("openapi.yaml") + + .. grid-item:: **openapi.yaml** + + .. code-block:: yaml - options = {"swagger_ui": False} - app = connexion.FlaskApp(__name__, specification_dir='openapi/', - options=options) - app.add_api('my_api.yaml') + openapi: "3.0.0" + ... + paths: + /greeting/{name}: + post: + operationId: run.post_greeting + responses: + 200: + content: + text/plain: + schema: + type: string + parameters: + - name: name + in: path + required: true + schema: + type: string +The operation described in your specification is automatically linked to your Python view function +via the :code:`operationId`. You can change this behavior using different :code:`Resolvers`, see +:doc:`routing`. When the endpoint is called, connexion will take care of routing, security, +request body and parameter parsing, and response serialization. All based on the specification. -You can also disable it at the API level: +You can add as many APIs as you want to a single application. The :code:`add_api()` method +provides a lot of configuration options. When an option is provided both to the App and the API, +the API value will take precedence. + +.. dropdown:: View a detailed reference of the options accepted by the :code:`add_api()` method + :icon: eye + + .. tab-set:: + + .. tab-item:: AsyncApp + :sync: AsyncApp + + .. autofunction:: connexion.AsyncApp.add_api + :noindex: + + .. tab-item:: FlaskApp + :sync: FlaskApp + + .. autofunction:: connexion.FlaskApp.add_api + :noindex: + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + .. autofunction:: connexion.ConnexionMiddleware.add_api + :noindex: + +Running your application +------------------------ + +You can run your application using an ASGI server such as `uvicorn`. If you defined your +:code:`app` in a python module called :code:`run.py`, you can run it as follows: + +.. code-block:: bash + + $ uvicorn run:app + +or if you installed connexion using :code:`connexion[uvicorn]`, you can run it using the +:code:`run` method. This is only recommended for development: .. code-block:: python - options = {"swagger_ui": False} - app = connexion.FlaskApp(__name__, specification_dir='openapi/') - app.add_api('my_api.yaml', options=options) + app.run() -You can pass custom Swagger UI `Configuration Parameters`_ like e.g. -`displayOperationId` through the `swagger_ui_config` option: +To leverage automatic reloading of your application, you need to provide the application as an +import string. In most cases, this can be achieved as follows: .. code-block:: python - options = {"swagger_ui_config": {"displayOperationId": True}} - app = connexion.FlaskApp(__name__, specification_dir='openapi/', - options=options) + from pathlib import Path + app.run(f"{Path(__file__).stem}:app") -.. _Configuration Parameters: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#parameters +.. dropdown:: View a detailed reference of the options accepted by the :code:`run()` method + :icon: eye -Server Backend + .. tab-set:: + + .. tab-item:: AsyncApp + :sync: AsyncApp + + .. autofunction:: connexion.AsyncApp.run + :noindex: + + .. tab-item:: FlaskApp + :sync: FlaskApp + + .. autofunction:: connexion.FlaskApp.run + :noindex: + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + .. autofunction:: connexion.ConnexionMiddleware.run + :noindex: + +The Swagger UI -------------- -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 -to ``tornado`` or ``gevent``: -.. code-block:: python +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. - import connexion +**https://localhost:{port}/{base_path}/ui/** - app = connexion.FlaskApp(__name__, port = 8080, specification_dir='openapi/', server='tornado') +.. image:: images/swagger_ui.png +Full App class reference +------------------------ -Additionally, you can pass the parameters for the corresponding server as a dictionary using the `server_args` parameter. For example, in a scenario where you're serving static content, you can pass the parameters as follows: +For more details on what you can do, view the complete API reference below. -.. code-block:: python +.. tab-set:: + + .. tab-item:: AsyncApp + :sync: AsyncApp + + .. dropdown:: View a detailed reference of the :code:`AsyncApp` + :icon: eye + + .. autoclass:: connexion.AsyncApp + :members: + :undoc-members: + :inherited-members: + + .. tab-item:: FlaskApp + :sync: FlaskApp + + .. dropdown:: View a detailed reference of the :code:`FlaskApp` + :icon: eye - import connexion + .. autoclass:: connexion.FlaskApp + :members: + :undoc-members: + :inherited-members: - app = connexion.FlaskApp( - __name__, - port=8080, - specification_dir='openapi/', - server='flask', - server_args={'static_url_path': '/', 'static_folder': 'wherever/your/static/files/are'} - ) + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + .. dropdown:: View a detailed reference of the :code:`ConnexionMiddleware` + :icon: eye -.. _Jinja2: http://jinja.pocoo.org/ -.. _Tornado: http://www.tornadoweb.org/en/stable/ -.. _gevent: http://www.gevent.org/ + .. autoclass:: connexion.ConnexionMiddleware + :members: + :undoc-members: diff --git a/pyproject.toml b/pyproject.toml index 04821dd69..70030476e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,8 @@ pytest-cov = "~2.12.1" [tool.poetry.group.docs.dependencies] sphinx = "5.3.0" -sphinx-autoapi = "2.0.1" +sphinx_copybutton = "0.5.2" +sphinx_design = "0.4.1" sphinx-rtd-theme = "1.2.0" [build-system]