Routing¶
Connexion leverages your OpenAPI contract to route requests to your python functions. This can be done in two ways:
Explicit routing¶
Connexion uses the operation_id
to link each operation in your API contract to
the python function that should handle it.
paths:
/hello_world:
post:
operationId: myapp.api.hello_world
Based on the operationId
above, any POST
request to
http://{HOST}/hello_world
, will be handled by the hello_world
function in the
myapp.api
module.
Optionally, you can include x-openapi-router-controller
or
x-swagger-router-controller
in your operationId
to make your operationId relative:
paths:
/hello_world:
post:
x-openapi-router-controller: myapp.api
operationId: hello_world
If all your operations are relative, you can use the RelativeResolver
class when
registering your API instead of repeating the same x-openapi-router-controller
in every
operation:
import connexion
from connexion.resolver import RelativeResolver
app = connexion.AsyncApp(__name__)
app.add_api('openapi.yaml', resolver=RelativeResolver('myapp.api'))
View a detailed reference of the RelativeResolver
class
- class connexion.resolver.RelativeResolver(root_path, function_resolver=<function get_function_from_name>)¶
Resolves endpoint functions relative to a given root path or module.
- Parameters:
root_path (Union[str, types.ModuleType]) – The root path relative to which an operationId is resolved. Can also be a module. Has the same effect as setting x-swagger-router-controller or x-openapi-router-controller equal to root_path for every operation individually.
function_resolver (types.FunctionType) – Function that resolves functions using an operationId
Note that HEAD
requests will be handled by the operationId
specified under the
GET
operation in the specification. Connexion.request.method
can be used to
determine which request was made. See ConnexionRequest
.
Automatic routing¶
Connexion can also automate the routing for you. You can choose from different Resolvers
implementing different resolution strategies.
RestyResolver¶
The RestyResolver
will infer an operationId
based on the path and HTTP method of
each operation in your specification:
import connexion
from connexion.resolver import RestyResolver
app = connexion.FlaskApp(__name__)
app.add_api('openapi.yaml', resolver=RestyResolver('api'))
paths:
/:
get:
# Implied operationId: api.get
/foo:
get:
# Implied operationId: api.foo.search
post:
# Implied operationId: api.foo.post
/foo/{id}:
get:
# Implied operationId: api.foo.get
put:
# Implied operationId: api.foo.put
copy:
# Implied operationId: api.foo.copy
delete:
# Implied operationId: api.foo.delete
/foo/{id}/bar:
get:
# Implied operationId: api.foo.bar.search
/foo/{id}/bar/{name}:
get:
# Implied operationId: api.foo.bar.get
RestyResolver
will give precedence to any operationId
encountered in the specification and
respects x-openapi-router-controller
and x-swagger-router-controller
.
View a detailed reference of the RestyResolver
class
- class connexion.resolver.RestyResolver(default_module_name: str, *, collection_endpoint_name: str = 'search')¶
Resolves endpoint functions using REST semantics (unless overridden by specifying operationId)
- Parameters:
default_module_name – Default module name for operations
collection_endpoint_name – Name of function to resolve collection endpoints to
MethodResolver¶
The MethodResolver
works like a RestyResolver
, but routes to class methods instead of
functions.
import connexion
from connexion.resolver import MethodResolver
app = connexion.FlaskApp(__name__)
app.add_api('openapi.yaml', resolver=MethodResolver('api'))
paths:
/foo:
get:
# Implied operationId: api.FooView.search
post:
# Implied operationId: api.FooView.post
'/foo/{id}':
get:
# Implied operationId: api.FooView.get
put:
# Implied operationId: api.FooView.put
copy:
# Implied operationId: api.FooView.copy
delete:
# Implied operationId: api.FooView.delete
The structure expects a Class to exists inside the api
module with the name
<<CapitalisedPath>>View
.
class PetsView:
def post(self, body: dict):
...
def put(self, petId, body: dict):
...
def delete(self, petId):
...
def get(self, petId=None):
...
def search(limit=100):
...
It is possible to use decorators for the Method view by listing them in the decorator attribute of the class:
def example_decorator(f):
def decorator(*args, **kwargs):
return f(*args, **kwargs)
return decorator
class PetsView:
"""Create Pets service"""
decorators = [example_decorator]
...
Additionally, you may inject dependencies into the class by declaring parameters
for this class in the __init__
method and providing the arguments in the
MethodViewResolver()
call. The arguments are passed down to the class when
as_view
is called.
A class might look like this:
class PetsView:
def __init__(self, pets):
self.pets = pets
And the arguments are provided like this:
MethodViewResolver("api", class_arguments={"PetsView": {"kwargs": {"pets": zoo}}})
MethodResolver
will give precedence to any operationId
encountered in the specification and
respects x-openapi-router-controller
and x-swagger-router-controller
.
View a detailed reference of the MethodResolver
class
- class connexion.resolver.MethodResolver(*args, class_arguments: Optional[Dict[str, Dict[str, Union[Iterable, Dict[str, Any]]]]] = None, **kwargs)¶
A generic method resolver that instantiates a class and extracts the method from it, based on the operation id.
- Parameters:
args – Arguments passed to
RestyResolver
class_arguments – Arguments to instantiate the View Class in the format below
kwargs – Keywords arguments passed to
RestyResolver
{ "ViewName": { "args": (positional arguments,) "kwargs": { "keyword": "argument" } } }
MethodViewResolver¶
The MethodResolver
works like a MethodViewResolver
, but routes to class methods of a
Flask MethodView
subclass.
Note
If you migrate from connexion v2 you may want to use the MethodResolver
in order to maintain
the old behavior. The behavior described here is the new behavior, introduced in connexion v3.
Previously, in v2, the MethodViewResolver
worked like the MethodResolver
in v3.
Another difference is that the MethodResolver
will look for search
and get
methods for collection and single item operations respectively, while MethodViewResolver
handles both collection and single item operations via the same get
method.
import connexion
from connexion.resolver import MethodResolver
app = connexion.FlaskApp(__name__)
app.add_api('openapi.yaml', resolver=MethodViewResolver('api'))
paths:
/foo:
get:
# Implied operationId: api.FooView.get
post:
# Implied operationId: api.FooView.post
'/foo/{id}':
get:
# Implied operationId: api.FooView.get
put:
# Implied operationId: api.FooView.put
copy:
# Implied operationId: api.FooView.copy
delete:
# Implied operationId: api.FooView.delete
The structure expects a Class to exists inside the api
module with the name
<<CapitalisedPath>>View
.
from flask.views import MethodView
class PetsView(MethodView):
def post(self, body: dict):
...
def put(self, petId, body: dict):
...
def delete(self, petId):
...
def get(self, petId=None, limit=100):
...
View a detailed reference of the MethodViewResolver
class
- class connexion.resolver.MethodViewResolver(*args, **kwargs)¶
A specialized method resolver that works with flask’s method views. It resolves the method by calling as_view on the class.
- Parameters:
args – Arguments passed to
RestyResolver
class_arguments – Arguments to instantiate the View Class in the format below
kwargs – Keywords arguments passed to
RestyResolver
{ "ViewName": { "args": (positional arguments,) "kwargs": { "keyword": "argument" } } }
Custom resolver¶
You can import and extend connexion.resolver.Resolver
to implement your own
operationId
and function resolution algorithm.
View a detailed reference of the RestyResolver
class
- class connexion.resolver.Resolver(function_resolver: ~typing.Callable = <function get_function_from_name>)¶
Standard resolver
- Parameters:
function_resolver – Function that resolves functions using an operationId
- resolve(operation)¶
Default operation resolver
- resolve_function_from_operation_id(operation_id)¶
Invokes the function_resolver
- resolve_operation_id(operation)¶
Default operationId resolver
Note
If you implement a custom Resolver
, and think it would be valuable for other users, we
would appreciate it as a contribution.
Resolver error¶
By default, Connexion strictly enforces the presence of a handler
function for any path defined in your specification. Because of this, adding
new paths without implementing a corresponding handler function will produce
runtime errors and your application will not start. To allow new paths to be
added to your specification, e.g. in an API design first workflow, set the
resolver_error
to configure Connexion to provide an error response for
paths that are not yet implemented:
app = connexion.FlaskApp(__name__)
app.add_api('openapi.yaml', resolver_error=501)
Path parameters¶
Path parameters are variable parts of a URL path denoted with curly braces { }
in the
specification.
paths:
/users/{id}:
parameters:
- in: path
name: id # Note the name is the same as in the path
required: true
schema:
type: integer
description: The user ID
paths:
/users/{id}:
parameters:
- in: path
name: id # Note the name is the same as in the path
required: true
type: integer
description: The user ID.
By default this will capture characters up to the end of the path or the next /.
You can use convertors to modify what is captured. The available convertors are:
str returns a string, and is the default.
int returns a Python integer.
float returns a Python float.
path returns the rest of the path, including any additional / characters.
Convertors are used by defining them as the format
in the parameter specification
Specify a route parameter’s type as integer
or number
or its type as
string
and its format as path
to use these converters.
Path parameters are passed as arguments to your python function.
Individual paths¶
You can also add individual paths to your application which are not described in your API
contract. This can be useful for eg. /healthz
or similar endpoints.
@app.route("/healthz")
def healthz():
return 200
# Or as alternative to the decorator
app.add_url_rule("/healthz", "healthz", healthz)
View a detailed reference of the route
and add_url_rule
methods
- AsyncApp.route(rule: str, **options)
A decorator that is used to register a view function for a given URL rule. This does the same thing as add_url_rule but is intended for decorator usage:
@app.route('/') def index(): return 'Hello World'
- Parameters:
rule – the URL rule as string
options – the options to be forwarded to the underlying
werkzeug.routing.Rule
object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GET, POST etc.). By default a rule just listens for GET (and implicitly HEAD).
- AsyncApp.add_url_rule(rule, endpoint: Optional[str] = None, view_func: Optional[Callable] = None, **options)
Connects a URL rule. Works exactly like the route decorator.
Basically this example:
@app.route('/') def index(): pass
Is equivalent to the following:
def index(): pass app.add_url_rule('/', 'index', index)
Internally`route` invokes add_url_rule so if you want to customize the behavior via subclassing you only need to change this method.
- Parameters:
rule – the URL rule as string.
endpoint – the name of the endpoint for the registered URL rule, which is used for reverse lookup. Flask defaults to the name of the view function.
view_func – the function to call when serving a request to the provided endpoint.
options – the options to be forwarded to the underlying
werkzeug.routing.Rule
object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GET, POST etc.). By default a rule just listens for GET (and implicitly HEAD).
View a detailed reference of the route
and add_url_rule
methods
- FlaskApp.route(rule: str, **options)
A decorator that is used to register a view function for a given URL rule. This does the same thing as add_url_rule but is intended for decorator usage:
@app.route('/') def index(): return 'Hello World'
- Parameters:
rule – the URL rule as string
options – the options to be forwarded to the underlying
werkzeug.routing.Rule
object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GET, POST etc.). By default a rule just listens for GET (and implicitly HEAD).
- FlaskApp.add_url_rule(rule, endpoint: Optional[str] = None, view_func: Optional[Callable] = None, **options)
Connects a URL rule. Works exactly like the route decorator.
Basically this example:
@app.route('/') def index(): pass
Is equivalent to the following:
def index(): pass app.add_url_rule('/', 'index', index)
Internally`route` invokes add_url_rule so if you want to customize the behavior via subclassing you only need to change this method.
- Parameters:
rule – the URL rule as string.
endpoint – the name of the endpoint for the registered URL rule, which is used for reverse lookup. Flask defaults to the name of the view function.
view_func – the function to call when serving a request to the provided endpoint.
options – the options to be forwarded to the underlying
werkzeug.routing.Rule
object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GET, POST etc.). By default a rule just listens for GET (and implicitly HEAD).
When using the ConnexionMiddleware
around an ASGI or WSGI application, you can
register individual routes on the wrapped application.
API Versioning and basePath¶
Setting a base path is useful for versioned APIs. An example of
a base path would be the 1.0
in http://{HOST}/1.0/hello_world
.
If you are using OpenAPI 3, you set your base URL path in the servers block of the specification. You can either specify a full URL, or just a relative path.
servers:
- url: https://{{HOST}}/1.0
description: full url example
- url: /1.0
description: relative path example
paths:
...
If you are using Swagger 2.0, you can define a basePath
on the top level
of your Swagger 2.0 specification.
basePath: /1.0
paths:
...
If you don’t want to include the base path in your specification, you can provide it when adding the API to your application:
app.add_api('openapi.yaml', base_path='/1.0')