Connexion 3.0: API-first for all

We are excited to announce the release of Connexion 3.0! 🎉

Connexion 3 fundamentally changes how Connexion is designed and implemented, and how it fits into the wider Python API ecosystem. We adopted the ASGI interface, which makes Connexion both modular and well-integrated with most modern Python API tooling.

It brings some major changes compared to 2.X:

  • The improved App and new AsyncApp allow you to use Connexion as a stand-alone framework

    • The App interface was extended so you no longer have to care about the framework used underneath

  • Connexion can now be used as middleware to supercharge any ASGI or WSGI-compatible framework with its spec-based functionality

  • Connexion is now pluggable in many dimensions:

    • All Connexion functionality is pluggable by adding or removing middleware from its stack

    • Validation is now pluggable by content type, solving longstanding issues regarding endpoints with multiple content types and making it easy to add validation for additional content types

    • Authentication is now pluggable by security scheme, making it easy to customize the behavior or add support for additional security schemes.

  • Aiohttp support has been dropped due to lack of ASGI support

  • We spent a lot of effort on extending and improving our documentation

Read on below to discover more changes. 👇

Or read our in-depth blog post on the redesign.

Getting started with Connexion 3

If you’re getting started with Connexion 3 for a new project, follow the quickstart. All documentation has been updated for Connexion 3.

Migrating from Connexion 2

The rest of this page will focus on how to migrate from Connexion 2 to Connexion 3.

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.

Running the application

There have been 2 changes related to running the application:

  • You now MUST run the Connexion application instead of the underlying Flask application.

  • You should use an ASGI server instead of a WSGI server.

While the following would work on Connexion 2, it no longer works on Connexion 3:

hello.py
import connexion

app = connexion.App(__name__)
flask_app = app.app

if __name__ == "__main__":
    flask_app.run()
$ flask --app hello:flask_app
$ gunicorn hello:flask_app

Instead, you need to run the Connexion application using an ASGI server:

hello.py
import connexion

app = connexion.App(__name__)

if __name__ == "__main__":
    app.run()
$ uvicorn run:app
$ 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 Running your application.

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:

  • 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:

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:

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:

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,
    )
View a detailed reference of the connexion.problem.problem function
connexion.problem.problem(status, title, detail, type=None, instance=None, headers=None, ext=None)

Returns a Problem Details error response.

Parameters:
  • status (int) – The HTTP status code generated by the origin server for this occurrence of the problem.

  • title (str) – A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localisation.

  • detail (str) – An human readable explanation specific to this occurrence of the problem.

  • type – An absolute URI that identifies the problem type. When dereferenced, it SHOULD provide human-readable documentation for the problem type (e.g., using HTML). When this member is not present its value is assumed to be “about:blank”.

  • instance (str) – An absolute URI that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.

  • headers (dict | None) – HTTP headers to include in the response

  • ext (dict | None) – Extension members to include in the body

Type:

type: str

Returns:

error response

Return type:

ConnexionResponse

For more information, check the Exception Handling documentation.

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 Connexion Cookbook: CORS on how to use a CORSMiddleware instead.

See 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.

You can use the connexion.datastructures.MediaTypeDict to support content type ranges.

VALIDATOR_MAP = {
    "parameter": ParameterValidator,
    "body": MediaTypeDict(
        {
            "*/*json": JSONRequestBodyValidator,
            "application/x-www-form-urlencoded": FormDataValidator,
            "multipart/form-data": MultiPartFormDataValidator,
        }
    ),
    "response": MediaTypeDict(
        {
            "*/*json": JSONResponseBodyValidator,
            "text/plain": TextResponseBodyValidator,
        }
    ),
}

You can pass it either to the app, or when registering an API.

app = connexion.App(__name__, validator_map=VALIDATOR_MAP)
app.add_api("openapi.yaml", validator_map=VALIDATOR_MAP)

An AbstractRequestBodyValidator and AbstractResponseBodyValidator class are available to support the creation of custom validators.

Swagger UI Options

The options argument has been renamed to swagger_ui_options and now takes an instance of the SwaggerUIOptions. The naming of the options themselves have been changed to better represent their meaning.

import connexion
from connexion.options import SwaggerUIOptions

swagger_ui_options = SwaggerUIOptions(
    swagger_ui=True,
    swagger_ui_path="docs",
)

app = connexion.FlaskApp(__name__, swagger_ui_options=swagger_ui_options)  # either
app.add_api("openapi.yaml", swagger_ui_options=swagger_ui_options)  # or

See The Swagger UI for more information.

Smaller breaking changes

  • 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 as an attribute on the Api.

  • Drop Flask 1.X support and support Flask 2.X async routes

  • Drop Python 3.6 (and add Python 3.10) support

  • connexion.request is now a Starlette Request instead of a Flask Request

  • Route priority changed. The most specific route should now be defined first in the specification.

  • We no longer guess a content type for response serialization if multiple are defined in the spec. We do take into account returned headers.

  • Don’t return 400 when read-only property is received

  • Content type is now validated for requests and responses if defined in the spec

  • The deprecated positions for x-body-name are no longer supported

  • The parameter pass_context_arg_name has been removed. Context is now available as global request-level context, or can be passed in by defining a context_ parameter in your view function.

  • The MethodViewResolver has been renamed to MethodResolver, and a new MethodViewResolver has been added to work with Flask’s MethodView specifically.

  • Built-in support for uWSGI has been removed. You can re-add this functionality using a custom middleware.

  • The request body is now passed through for GET, HEAD, DELETE, CONNECT and OPTIONS methods as well.

  • The signature of error handlers has changed and default Flask error handlers are now replaced with default Connexion error handlers which work the same for AsyncApp and ConnexionMiddleware.

Non-breaking changes

  • Relative and nested refs are now supported in OpenAPI / Swagger specifications

  • The required keyword is now supported for requestBodies

  • HTTP exceptions are now implemented as a hierarchy

  • Connexion now exposes context, operation, receive, scope as global request-level context

  • Connexion now provides a DefaultsJSONRequestBodyValidator to fill in default values in received request bodies.

Full changelog

Consult our Github release page for an overview of all changes.

Feedback

We would really love to hear from you, so let us know if you have any feedback or questions. We’d like to make the migration for our users as easy and possible.

  • For questions, comments, and feedback, please comment on the discussion which will be created and pinned after the release.

  • For issues, please open an issue on our Github tracker