diff --git a/docs/v3.rst b/docs/v3.rst index db8e87e11..5334380af 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -37,38 +37,176 @@ Or read our `in-depth blog post`_ on the redesign. Getting started with Connexion 3 -------------------------------- -Using stand-alone Connexion ---------------------------- +If you're getting started with Connexion 3 for a new project, follow the +`quickstart `_. All documentation has been updated for Connexion 3. -You can use Connexion as a stand-alone web framework, using one of the available apps: +Migrating from Connexion 2 +-------------------------- -* The ``App`` (alias ``FlaskApp``), which is built on top of Flask as known from Connexion 2.X. -* The ``AsyncApp``, which is built on top of starlette and provides native asynchronous functionality. +The rest of this page will focus on how to migrate from Connexion 2 to Connexion 3. -If you don't require compatibility with the Flask ecosystem, we recommend to use the ``AsyncApp``. -Even when writing mostly synchronous code, as you can just use synchronous view functions. +This page will show examples migrating the ``connexion.FlaskApp``. However all Connexion 3 examples +should work for ``connexion.AsyncApp`` as well. If you are not relying on the underlying +Flask application, or you are coming from the old ``AiohttpApp``, we recommend migrating to the +``connexion.AsyncApp`` instead. -Using Connexion with ASGI or WSGI frameworks --------------------------------------------- +Running the application +''''''''''''''''''''''' -If you want to leverage Connexion functionality with third party ASGI frameworks, you can use the -``ConnexionMiddleware`` and wrap it around a third party application. +There have been 2 changes related to running the application: -This provides all Connexion functionality except for automatic routing, automatic parameter injection, -and response serialization. You can add some of this functionality using ``Decorators`` provided by -Connexion: +- You now MUST run the Connexion application instead of the underlying Flask application. +- You should use an ASGI server instead of a WSGI server. -* ``FlaskDecorator``: provides automatic parameter injection and response serialization for Flask - applications. -* ``ASGIDecorator``: provides automatic parameter injection for ASGI applications. Note that this - decorator injects Starlette datastructures (such as ``UploadFile``). -* ``StarletteDecorator``: provides automatic parameter injection and response serialization for - Starlette applications. +While the following would work on Connexion 2, it no longer works on Connexion 3: -For examples, see https://github.com/spec-first/connexion/tree/main/examples/frameworks. +.. code-block:: python + :caption: **hello.py** + + import connexion + + app = connexion.App(__name__) + flask_app = app.app + + if __name__ == "__main__": + flask_app.run() + +.. code-block:: bash + + $ flask --app hello:flask_app + +.. code-block:: bash + + $ gunicorn hello:flask_app + + +Instead, you need to run the Connexion application using an ASGI server: + +.. code-block:: python + :caption: **hello.py** + + import connexion + + app = connexion.App(__name__) + + if __name__ == "__main__": + app.run() + +.. code-block:: bash + + $ uvicorn run:app + +.. code-block:: bash + + $ gunicorn -k uvicorn.workers.UvicornWorker run:app + +.. warning:: + + You can wrap Connexion with the `ASGIMiddleware`_ offered by `a2wsgi`_ to run it with a WSGI + server. You will however lose the benefits offered by ASGI, and performance might be + impacted. You should only use this as a temporary workaround until you can switch to an ASGI + server. + +For more information, check :ref:`Running your application `. + +.. _ASGIMiddleware: https://github.com/abersheeran/a2wsgi#convert-asgi-app-to-wsgi-app +.. _a2wsgi: https://github.com/abersheeran/a2wsgi + +**Workers and threads** + +You can still use workers as before, however you should not use threads with ASGI, since it +handles concurrency using an async event loop instead. + +In the ``AsyncApp``, concurrency is completely handled by the async event loop. + +The ``FlaskApp`` is more complex, since the underlying Flask app is WSGI instead of ASGI. +Concurrency in the middleware stack is handled by the async event loop, but once a request is +passed to the underlying Flask app, it is executed in a thread pool (of 10 workers) automatically. + +Error handlers +`````````````` + +There have been 2 changes related to running the application: -Pluggable validation by content type ------------------------------------- +- The interface of the error handlers changed, with a request now being injected as well +- The error handlers now should be registered on the Connexion App, not the underlying Flask App + +Connexion 2: + +.. code-block:: python + :caption: **hello.py** + + import connexion + + def not_found_handler(exc: Exception) -> flask.Response: + ... + + app = connexion.App(__name__) + flask_app = app.app + + app.add_error_handler(404, not_found_handler) # either + flask_app.register_error_handler(404, not_found_handler) # or + +Connexion 3: + +.. code-block:: python + :caption: **hello.py** + + import connexion + from connexion.lifecycle import ConnexionRequest, ConnexionResponse + + def not_found_handler(request: ConnexionRequest, exc: Exception) -> ConnexionResponse: + ... + + app = connexion.App(__name__) + app.add_error_handler(404, not_found_handler) + +You can easily generate Connexion responses adhering to the `Problem Details for HTTP APIs`_ +standard by using the ``connexion.problem.problem`` module: + +.. code-block:: python + + from connexion.problem import problem + + def not_found_handler(request: ConnexionRequest, exc: Exception) -> ConnexionResponse: + return problem( + title=http_facts.HTTP_STATUS_CODES.get(404), + detail="The resource was not found", + status=404, + ) + + +.. dropdown:: View a detailed reference of the ``connexion.problem.problem`` function + :icon: eye + + .. autofunction:: connexion.problem.problem + :noindex: + +For more information, check the :doc:`exceptions` documentation. + +.. _Problem Details for HTTP APIs: https://datatracker.ietf.org/doc/html/rfc7807 + +Flask extensions and WSGI middleware +```````````````````````````````````` + +Certain Flask extensions and WSGI middleware might no longer work, since some functionaity was +moved outside the scope of the Flask application. Extensions and middleware impacting the +following functionality should now be implemented as ASGI middleware instead: + +- Exception handling +- Swagger UI +- Routing +- Security +- Validation + +One such example is CORS support, since it impacts routing. It can no longer be added via the +``Flask-Cors`` extension. See :ref:`Connexion Cookbook: CORS ` on how to use a +``CORSMiddleware`` instead. + +See :doc:`middleware` for general documentation on ASGI middleware. + +Custom validators +````````````````` Validation is now pluggable by content type, which means that the `VALIDATOR_MAP` has been updated to accommodate this. @@ -104,35 +242,31 @@ You can pass it either to the app, or when registering an API. An ``AbstractRequestBodyValidator`` and ``AbstractResponseBodyValidator`` class are available to support the creation of custom validators. -ASGI Server ------------ +Swagger UI Options +------------------ -Connexion 3.0 needs to be run using an ASGI server instead of a WSGI server. While any ASGI server -should work, connexion comes with ``uvicorn`` as an extra: +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. -.. code-block:: bash +.. code-block:: python - pip install connexion[uvicorn] + import connexion + from connexion.options import SwaggerUIOptions -Check :ref:`quickstart:Running your application` for more details on how to run your application -using an ASGI server. + swagger_ui_options = SwaggerUIOptions( + swagger_ui=True, + swagger_ui_path="docs", + ) -.. warning:: + app = connexion.FlaskApp(__name__, swagger_ui_options=swagger_ui_options) # either + app.add_api("openapi.yaml", swagger_ui_options=swagger_ui_options) # or - You can wrap Connexion with the `ASGIMiddleware`_ offered by `a2wsgi`_ to run it with a WSGI - server. You will however lose the benefits offered by ASGI, and performance might be - impacted. You should only use this as a temporary workaround until you can switch to an ASGI - server. - -.. _ASGIMiddleware: https://github.com/abersheeran/a2wsgi#convert-asgi-app-to-wsgi-app -.. _a2wsgi: https://github.com/abersheeran/a2wsgi +See :doc:`swagger_ui` for more information. Smaller breaking changes ------------------------ -* 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