mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-09 20:37:46 +00:00
Update routing documentation (#1738)
Works towards #1531 Some parts of the old outing docs will need to be included on the `parameters` and `swagger-ui` pages which we still need to add.
This commit is contained in:
@@ -214,7 +214,7 @@ class AbstractApp:
|
|||||||
:param endpoint: the name of the endpoint for the registered URL rule, which is used for
|
:param 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.
|
reverse lookup. Flask defaults to the name of the view function.
|
||||||
:param view_func: the function to call when serving a request to the provided endpoint.
|
:param view_func: the function to call when serving a request to the provided endpoint.
|
||||||
:param options: the options to be forwarded to the underlying `werkzeug.routing.Rule`
|
:param 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
|
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
|
methods this rule should be limited to (`GET`, `POST` etc.). By default a rule just
|
||||||
listens for `GET` (and implicitly `HEAD`).
|
listens for `GET` (and implicitly `HEAD`).
|
||||||
@@ -231,7 +231,7 @@ class AbstractApp:
|
|||||||
return 'Hello World'
|
return 'Hello World'
|
||||||
|
|
||||||
:param rule: the URL rule as string
|
:param rule: the URL rule as string
|
||||||
:param options: the options to be forwarded to the underlying `werkzeug.routing.Rule`
|
:param 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
|
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.).
|
list of methods this rule should be limited to (`GET`, `POST` etc.).
|
||||||
By default a rule just listens for `GET` (and implicitly `HEAD`).
|
By default a rule just listens for `GET` (and implicitly `HEAD`).
|
||||||
|
|||||||
@@ -22,27 +22,41 @@ class _RequestInterface:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def content_type(self) -> str:
|
def content_type(self) -> str:
|
||||||
|
"""The content type included in the request headers."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mimetype(self) -> str:
|
def mimetype(self) -> str:
|
||||||
|
"""The content type included in the request headers stripped from any optional character
|
||||||
|
set encoding"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_params(self) -> t.Dict[str, t.Any]:
|
def path_params(self) -> t.Dict[str, t.Any]:
|
||||||
|
"""Path parameters exposed as a dictionary"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def query_params(self) -> t.Dict[str, t.Any]:
|
def query_params(self) -> t.Dict[str, t.Any]:
|
||||||
|
"""Query parameters exposed as a dictionary"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def form(self) -> t.Union[t.Dict[str, t.Any], t.Awaitable[t.Dict[str, t.Any]]]:
|
def form(self) -> t.Union[t.Dict[str, t.Any], t.Awaitable[t.Dict[str, t.Any]]]:
|
||||||
|
"""Form data, including files."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def files(self) -> t.Dict[str, t.Any]:
|
def files(self) -> t.Dict[str, t.Any]:
|
||||||
|
"""Files included in the request."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def json(self) -> dict:
|
||||||
|
"""Json data included in the request."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_body(self) -> t.Any:
|
def get_body(self) -> t.Any:
|
||||||
|
"""Get body based on the content type. This returns json data for json content types,
|
||||||
|
form data for form content types, and bytes for all others. If the bytes data is emtpy,
|
||||||
|
:code:`None` is returned instead."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@@ -98,8 +112,10 @@ class WSGIRequest(_RequestInterface):
|
|||||||
def files(self):
|
def files(self):
|
||||||
return self._werkzeug_request.files.to_dict(flat=False)
|
return self._werkzeug_request.files.to_dict(flat=False)
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.get_json(silent=True)
|
||||||
|
|
||||||
def get_body(self):
|
def get_body(self):
|
||||||
"""Get body based on content type"""
|
|
||||||
if self._body is None:
|
if self._body is None:
|
||||||
if is_json_mimetype(self.content_type):
|
if is_json_mimetype(self.content_type):
|
||||||
self._body = self.get_json(silent=True)
|
self._body = self.get_json(silent=True)
|
||||||
@@ -115,7 +131,15 @@ class WSGIRequest(_RequestInterface):
|
|||||||
|
|
||||||
|
|
||||||
class ASGIRequest(_RequestInterface):
|
class ASGIRequest(_RequestInterface):
|
||||||
"""Wraps starlette Request so it can easily be extended."""
|
"""
|
||||||
|
Implementation of the Connexion :code:`_RequestInterface` representing an ASGI request.
|
||||||
|
|
||||||
|
.. attribute:: _starlette_request
|
||||||
|
|
||||||
|
This class wraps a Starlette `Request <https://www.starlette.io/requests/#request>`_,
|
||||||
|
and provides access to its attributes by proxy.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, uri_parser=None, **kwargs):
|
def __init__(self, *args, uri_parser=None, **kwargs):
|
||||||
self._starlette_request = StarletteRequest(*args, **kwargs)
|
self._starlette_request = StarletteRequest(*args, **kwargs)
|
||||||
|
|||||||
@@ -114,10 +114,12 @@ class RestyResolver(Resolver):
|
|||||||
Resolves endpoint functions using REST semantics (unless overridden by specifying operationId)
|
Resolves endpoint functions using REST semantics (unless overridden by specifying operationId)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default_module_name, collection_endpoint_name="search"):
|
def __init__(
|
||||||
|
self, default_module_name: str, *, collection_endpoint_name: str = "search"
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param default_module_name: Default module name for operations
|
:param default_module_name: Default module name for operations
|
||||||
:type default_module_name: str
|
:param collection_endpoint_name: Name of function to resolve collection endpoints to
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.default_module_name = default_module_name
|
self.default_module_name = default_module_name
|
||||||
@@ -185,18 +187,23 @@ class RestyResolver(Resolver):
|
|||||||
|
|
||||||
class MethodResolverBase(RestyResolver):
|
class MethodResolverBase(RestyResolver):
|
||||||
"""
|
"""
|
||||||
Resolves endpoint functions based on Flask's MethodView semantics, e.g. ::
|
Resolves endpoint functions based on Flask's MethodView semantics, e.g.
|
||||||
|
|
||||||
paths:
|
.. code-block:: yaml
|
||||||
/foo_bar:
|
|
||||||
get:
|
paths:
|
||||||
# Implied function call: api.FooBarView().get
|
/foo_bar:
|
||||||
|
get:
|
||||||
|
# Implied function call: api.FooBarView().get
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class FooBarView(MethodView):
|
||||||
|
def get(self):
|
||||||
|
return ...
|
||||||
|
def post(self):
|
||||||
|
return ...
|
||||||
|
|
||||||
class FooBarView(MethodView):
|
|
||||||
def get(self):
|
|
||||||
return ...
|
|
||||||
def post(self):
|
|
||||||
return ...
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_class_arguments_type = t.Dict[
|
_class_arguments_type = t.Dict[
|
||||||
@@ -205,24 +212,22 @@ class MethodResolverBase(RestyResolver):
|
|||||||
|
|
||||||
def __init__(self, *args, class_arguments: _class_arguments_type = None, **kwargs):
|
def __init__(self, *args, class_arguments: _class_arguments_type = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param class_arguments: Arguments to instantiate the View Class in the format # noqa
|
:param args: Arguments passed to :class:`~RestyResolver`
|
||||||
{
|
:param class_arguments: Arguments to instantiate the View Class in the format below
|
||||||
"ViewName": {
|
:param kwargs: Keywords arguments passed to :class:`~RestyResolver`
|
||||||
"args": (positional arguments,)
|
|
||||||
"kwargs": {
|
.. code-block:: python
|
||||||
"keyword": "argument"
|
|
||||||
}
|
{
|
||||||
}
|
"ViewName": {
|
||||||
}
|
"args": (positional arguments,)
|
||||||
|
"kwargs": {
|
||||||
|
"keyword": "argument"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
self.class_arguments = class_arguments or {}
|
self.class_arguments = class_arguments or {}
|
||||||
if "collection_endpoint_name" in kwargs:
|
|
||||||
del kwargs["collection_endpoint_name"]
|
|
||||||
# Dispatch of request is done by Flask
|
|
||||||
logger.warning(
|
|
||||||
"collection_endpoint_name is ignored by the MethodViewResolver. "
|
|
||||||
"Requests to a collection endpoint will be routed to .get()"
|
|
||||||
)
|
|
||||||
super(MethodResolverBase, self).__init__(*args, **kwargs)
|
super(MethodResolverBase, self).__init__(*args, **kwargs)
|
||||||
self.initialized_views: list = []
|
self.initialized_views: list = []
|
||||||
|
|
||||||
@@ -280,7 +285,7 @@ class MethodResolverBase(RestyResolver):
|
|||||||
|
|
||||||
class MethodResolver(MethodResolverBase):
|
class MethodResolver(MethodResolverBase):
|
||||||
"""
|
"""
|
||||||
A generic method resolver that instantiates a class a extracts the method
|
A generic method resolver that instantiates a class and extracts the method
|
||||||
from it, based on the operation id.
|
from it, based on the operation id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -310,6 +315,16 @@ class MethodViewResolver(MethodResolverBase):
|
|||||||
It resolves the method by calling as_view on the class.
|
It resolves the method by calling as_view on the class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if "collection_endpoint_name" in kwargs:
|
||||||
|
del kwargs["collection_endpoint_name"]
|
||||||
|
# Dispatch of request is done by Flask
|
||||||
|
logger.warning(
|
||||||
|
"collection_endpoint_name is ignored by the MethodViewResolver. "
|
||||||
|
"Requests to a collection endpoint will be routed to .get()"
|
||||||
|
)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def resolve_method_from_class(self, view_name, meth_name, view_cls):
|
def resolve_method_from_class(self, view_name, meth_name, view_cls):
|
||||||
view = None
|
view = None
|
||||||
for v in self.initialized_views:
|
for v in self.initialized_views:
|
||||||
|
|||||||
4
docs/_static/css/default.css
vendored
Normal file
4
docs/_static/css/default.css
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.rst-content .code-block-caption {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px, 0px, 5px, 5px;
|
||||||
|
}
|
||||||
17
docs/conf.py
17
docs/conf.py
@@ -111,6 +111,17 @@ try:
|
|||||||
html_theme = "sphinx_rtd_theme"
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
|
||||||
|
html_theme_options = {
|
||||||
|
'navigation_depth': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
html_context = {
|
||||||
|
'display_github': True,
|
||||||
|
'github_user': 'spec-first',
|
||||||
|
'github_repo': 'connexion',
|
||||||
|
'github_version': 'main/docs/',
|
||||||
|
}
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -141,7 +152,11 @@ except:
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
# html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
html_css_files = [
|
||||||
|
'css/default.css',
|
||||||
|
]
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
Welcome to Connexion's documentation!
|
Welcome to Connexion's documentation!
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
Connexion is a Python web framework that makes spec-first and api-first development easy. You
|
Connexion is a modern Python web framework that makes spec-first and api-first development easy. You
|
||||||
describe your API in an `OpenAPI`_ (or swagger) specification with as much detail as you want and
|
describe your API in an `OpenAPI`_ (or swagger) specification with as much detail as you want and
|
||||||
Connexion will guarantee that it works as you specified.
|
Connexion will guarantee that it works as you specified.
|
||||||
|
|
||||||
|
|||||||
717
docs/routing.rst
717
docs/routing.rst
@@ -1,40 +1,35 @@
|
|||||||
Routing
|
Routing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Endpoint Routing to Your Python Views
|
Connexion leverages your OpenAPI contract to route requests to your python functions. This can
|
||||||
-------------------------------------
|
be done in two ways:
|
||||||
|
|
||||||
Connexion uses the ``operationId`` from each `Operation Object`_ to
|
* `Explicitly <#explicit-routing>`_
|
||||||
identify which Python function should handle each URL.
|
* `Automatically <#automatic-routing>`_
|
||||||
|
|
||||||
**Explicit Routing**:
|
Explicit routing
|
||||||
|
----------------
|
||||||
|
|
||||||
.. code-block:: yaml
|
Connexion uses the :code:`operation_id` to link each `operation`_ in your API contract to
|
||||||
|
the python function that should handle it.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **openapi.yaml**
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/hello_world:
|
/hello_world:
|
||||||
post:
|
post:
|
||||||
operationId: myapp.api.hello_world
|
operationId: myapp.api.hello_world
|
||||||
|
|
||||||
If you provided this path in your specification POST requests to
|
Based on the :code:`operationId` above, any :code:`POST` request to
|
||||||
``http://MYHOST/hello_world``, it would be handled by the function
|
:code:`http://{HOST}/hello_world`, will be handled by the :code:`hello_world` function in the
|
||||||
``hello_world`` in ``myapp.api`` module.
|
:code:`myapp.api` module.
|
||||||
|
|
||||||
Optionally, you can include ``x-swagger-router-controller`` in your operation
|
Optionally, you can include :code:`x-openapi-router-controller` or
|
||||||
definition, making ``operationId`` relative:
|
:code:`x-swagger-router-controller` in your :code:`operationId` to make your `operationId` relative:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: python
|
||||||
|
:caption: **openapi.yaml**
|
||||||
paths:
|
|
||||||
/hello_world:
|
|
||||||
post:
|
|
||||||
x-swagger-router-controller: myapp.api
|
|
||||||
operationId: hello_world
|
|
||||||
|
|
||||||
NOTE: If you are using an OpenAPI spec, you should use ``x-openapi-router-controller``
|
|
||||||
in your operation definition, making ``operationId`` relative:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/hello_world:
|
/hello_world:
|
||||||
@@ -42,22 +37,298 @@ in your operation definition, making ``operationId`` relative:
|
|||||||
x-openapi-router-controller: myapp.api
|
x-openapi-router-controller: myapp.api
|
||||||
operationId: hello_world
|
operationId: hello_world
|
||||||
|
|
||||||
If all your operations are relative, you can use the ``RelativeResolver`` class
|
|
||||||
instead of repeating the same ``x-swagger-router-controller`` or
|
If all your operations are relative, you can use the :code:`RelativeResolver` class when
|
||||||
``x-openapi-router-controller`` in every operation:
|
registering your API instead of repeating the same :code:`x-openapi-router-controller` in every
|
||||||
|
operation:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
|
import connexion
|
||||||
from connexion.resolver import RelativeResolver
|
from connexion.resolver import RelativeResolver
|
||||||
|
|
||||||
|
app = connexion.AsyncApp(__name__)
|
||||||
|
app.add_api('openapi.yaml', resolver=RelativeResolver('myapp.api'))
|
||||||
|
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`RelativeResolver` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.resolver.RelativeResolver
|
||||||
|
|
||||||
|
Note that :code:`HEAD` requests will be handled by the :code:`operationId` specified under the
|
||||||
|
:code:`GET` operation in the specification. :code:`Connexion.request.method` can be used to
|
||||||
|
determine which request was made.
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`connexion.request` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.lifecycle.ASGIRequest
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
Automatic routing
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Connexion can also automate the routing for you. You can choose from different :code:`Resolvers`
|
||||||
|
implementing different resolution strategies.
|
||||||
|
|
||||||
|
RestyResolver
|
||||||
|
`````````````
|
||||||
|
|
||||||
|
The :code:`RestyResolver` will infer an :code:`operationId` based on the path and HTTP method of
|
||||||
|
each operation in your specification:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
|
import connexion
|
||||||
|
from connexion.resolver import RestyResolver
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__)
|
app = connexion.FlaskApp(__name__)
|
||||||
app.add_api('swagger.yaml', resolver=RelativeResolver('api'))
|
app.add_api('openapi.yaml', resolver=RestyResolver('api'))
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
:caption: **openapi.yaml**
|
||||||
|
|
||||||
|
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``.
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`RestyResolver` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.resolver.RestyResolver
|
||||||
|
|
||||||
|
MethodResolver
|
||||||
|
``````````````
|
||||||
|
|
||||||
|
The ``MethodResolver`` works like a ``RestyResolver``, but routes to class methods instead of
|
||||||
|
functions.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
|
import connexion
|
||||||
|
from connexion.resolver import MethodResolver
|
||||||
|
|
||||||
|
app = connexion.FlaskApp(__name__)
|
||||||
|
app.add_api('openapi.yaml', resolver=MethodResolver('api'))
|
||||||
|
|
||||||
|
|
||||||
Keep in mind that Connexion follows how `HTTP methods work in Flask`_
|
.. code-block:: yaml
|
||||||
and therefore HEAD requests will be handled by the ``operationId`` specified
|
:caption: **openapi.yaml**
|
||||||
under GET in the specification. If both methods are supported,
|
|
||||||
``connexion.request.method`` can be used to determine which request was made.
|
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``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **api.py**
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **api.py**
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **api.py**
|
||||||
|
|
||||||
|
class PetsView:
|
||||||
|
def __init__(self, pets):
|
||||||
|
self.pets = pets
|
||||||
|
|
||||||
|
|
||||||
|
And the arguments are provided like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
|
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``.
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`MethodResolver` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.resolver.MethodResolver
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
|
import connexion
|
||||||
|
from connexion.resolver import MethodResolver
|
||||||
|
|
||||||
|
app = connexion.FlaskApp(__name__)
|
||||||
|
app.add_api('openapi.yaml', resolver=MethodViewResolver('api'))
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
:caption: **openapi.yaml**
|
||||||
|
|
||||||
|
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``.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:caption: **api.py**
|
||||||
|
|
||||||
|
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):
|
||||||
|
...
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`MethodViewResolver` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.resolver.MethodViewResolver
|
||||||
|
|
||||||
|
Custom resolver
|
||||||
|
```````````````
|
||||||
|
|
||||||
|
You can import and extend ``connexion.resolver.Resolver`` to implement your own
|
||||||
|
``operationId`` and function resolution algorithm.
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the :code:`RestyResolver` class
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. autoclass:: connexion.resolver.Resolver
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. 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
|
By default, Connexion strictly enforces the presence of a handler
|
||||||
function for any path defined in your specification. Because of this, adding
|
function for any path defined in your specification. Because of this, adding
|
||||||
@@ -68,293 +339,125 @@ added to your specification, e.g. in an API design first workflow, set the
|
|||||||
paths that are not yet implemented:
|
paths that are not yet implemented:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__)
|
app = connexion.FlaskApp(__name__)
|
||||||
app.add_api('swagger.yaml', resolver_error=501)
|
app.add_api('openapi.yaml', resolver_error=501)
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
Automatic Routing
|
Path parameters
|
||||||
-----------------
|
---------------
|
||||||
|
|
||||||
To customize this behavior, Connexion can use alternative
|
`Path parameters`_ are variable parts of a URL path denoted with curly braces ``{ }`` in the
|
||||||
``Resolvers``—for example, ``RestyResolver``. The ``RestyResolver``
|
specification.
|
||||||
will compose an ``operationId`` based on the path and HTTP method of
|
|
||||||
the endpoints in your specification:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. tab-set::
|
||||||
|
|
||||||
from connexion.resolver import RestyResolver
|
.. tab-item:: OpenAPI 3
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__)
|
.. code-block:: yaml
|
||||||
app.add_api('swagger.yaml', resolver=RestyResolver('api'))
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
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:
|
.. tab-item:: Swagger 2
|
||||||
/:
|
|
||||||
get:
|
|
||||||
# Implied operationId: api.get
|
|
||||||
/foo:
|
|
||||||
get:
|
|
||||||
# Implied operationId: api.foo.search
|
|
||||||
post:
|
|
||||||
# Implied operationId: api.foo.post
|
|
||||||
|
|
||||||
'/foo/{id}':
|
.. code-block:: yaml
|
||||||
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
|
|
||||||
# Handler signature: `def get(id, name): ...`
|
|
||||||
|
|
||||||
``RestyResolver`` will give precedence to any ``operationId``
|
paths:
|
||||||
encountered in the specification. It will also respect
|
/users/{id}:
|
||||||
``x-swagger-router-controller`` and ``x-openapi-router-controller``.
|
parameters:
|
||||||
You may import and extend ``connexion.resolver.Resolver`` to implement your own
|
- in: path
|
||||||
``operationId`` (and function) resolution algorithm.
|
name: id
|
||||||
Note that when using multiple parameters in the path, they will be
|
required: true
|
||||||
collected and all passed to the endpoint handlers.
|
type: integer
|
||||||
|
description: The user ID.
|
||||||
|
|
||||||
Automatic Routing with MethodViewResolver
|
By default this will capture characters up to the end of the path or the next `/`.
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
.. note::
|
You can use convertors to modify what is captured. The available convertors are:
|
||||||
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. The difference is that the `MethodResolver` works with any
|
|
||||||
class, while the `MethodViewResolver` is specifically designed to work with flask's
|
|
||||||
`MethodView`. Previously, in v2, the `MethodViewResolver` worked like the `MethodResolver`
|
|
||||||
in v3. One consequence is that the `MethodResolver` will look for `search` and `get`
|
|
||||||
methods for list and single operations respectively, while `MethodViewResolver` uses
|
|
||||||
the `dispatch_request` method of the given class and therefore handles both, list and
|
|
||||||
single operations via the same `get` method.
|
|
||||||
|
|
||||||
``MethodViewResolver`` is an customised Resolver based on ``RestyResolver``
|
* `str` returns a string, and is the default.
|
||||||
to take advantage of MethodView structure of building Flask APIs.
|
* `int` returns a Python integer.
|
||||||
The ``MethodViewResolver`` will compose an ``operationId`` based on the path and HTTP method of
|
* `float` returns a Python float.
|
||||||
the endpoints in your specification. The path will be based on the path you provide in the app.add_api and the path provided in the URL endpoint (specified in the swagger or openapi3).
|
* `path` returns the rest of the path, including any additional `/` characters.
|
||||||
|
|
||||||
.. code-block:: python
|
Convertors are used by defining them as the ``format`` in the parameter specification
|
||||||
|
|
||||||
from connexion.resolver import MethodViewResolver
|
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__)
|
|
||||||
app.add_api('swagger.yaml', resolver=MethodViewResolver('api'))
|
|
||||||
|
|
||||||
And associated YAML
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
|
|
||||||
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 directory ``api`` that conforms to the naming ``<<Classname with Capitalised name>>View``.
|
|
||||||
In the above yaml the necessary MethodView implementation is as follows:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from connexion import NoContent
|
|
||||||
from flask.views import MethodView
|
|
||||||
|
|
||||||
|
|
||||||
class PetsView(MethodView):
|
|
||||||
"""Create Pets service"""
|
|
||||||
|
|
||||||
pets = {}
|
|
||||||
|
|
||||||
def post(self, body: dict):
|
|
||||||
name = body.get("name")
|
|
||||||
tag = body.get("tag")
|
|
||||||
count = len(self.pets)
|
|
||||||
pet = {}
|
|
||||||
pet["id"] = count + 1
|
|
||||||
pet["tag"] = tag
|
|
||||||
pet["name"] = name
|
|
||||||
pet["last_updated"] = datetime.datetime.now()
|
|
||||||
self.pets[pet["id"]] = pet
|
|
||||||
return pet, 201
|
|
||||||
|
|
||||||
def put(self, petId, body: dict):
|
|
||||||
name = body["name"]
|
|
||||||
tag = body.get("tag")
|
|
||||||
pet = self.pets.get(petId, {"id": petId})
|
|
||||||
pet["name"] = name
|
|
||||||
pet["tag"] = tag
|
|
||||||
pet["last_updated"] = datetime.datetime.now()
|
|
||||||
self.pets[petId] = pet
|
|
||||||
return self.pets[petId], 201
|
|
||||||
|
|
||||||
def delete(self, petId):
|
|
||||||
id_ = int(petId)
|
|
||||||
if self.pets.get(id_) is None:
|
|
||||||
return NoContent, 404
|
|
||||||
del self.pets[id_]
|
|
||||||
return NoContent, 204
|
|
||||||
|
|
||||||
def get(self, petId=None, limit=100):
|
|
||||||
if petId is None:
|
|
||||||
# NOTE: we need to wrap it with list for Python 3 as
|
|
||||||
# dict_values is not JSON serializable
|
|
||||||
return list(self.pets.values())[0:limit]
|
|
||||||
if self.pets.get(petId) is None:
|
|
||||||
return NoContent, 404
|
|
||||||
return self.pets[petId]
|
|
||||||
|
|
||||||
|
|
||||||
and a __init__.py file to make the Class visible in the api directory.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from .petsview import PetsView
|
|
||||||
|
|
||||||
|
|
||||||
The `as_view` method of the class is called to create the view function.
|
|
||||||
Its `dispatch_request` method is used to route requests based on the HTTP method.
|
|
||||||
Therefore it is required to use the same `get` method for both, collection and
|
|
||||||
single resources. I.E. `/pets` and `/pets/{id}`.
|
|
||||||
|
|
||||||
It is possible to use decorators for the Method view by listing them in the
|
|
||||||
decorator attribute of the class:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def example_decorator(f):
|
|
||||||
|
|
||||||
def decorator(*args, **kwargs):
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
class PetsView(MethodView):
|
|
||||||
"""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:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class PetsView(MethodView):
|
|
||||||
def __init__(self, pets):
|
|
||||||
self.pets = pets
|
|
||||||
|
|
||||||
|
|
||||||
And the arguments are provided like this:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
MethodViewResolver("api", class_arguments={"PetsView": {"kwargs": {"pets": zoo}}})
|
|
||||||
|
|
||||||
|
|
||||||
``MethodViewResolver`` will give precedence to any ``operationId``
|
|
||||||
encountered in the specification. It will also respect
|
|
||||||
``x-swagger-router-controller`` and ``x-openapi-router-controller``.
|
|
||||||
You may import and extend ``connexion.resolver.MethodViewResolver`` to implement
|
|
||||||
your own ``operationId`` (and function) resolution algorithm.
|
|
||||||
|
|
||||||
Parameter Name Sanitation
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The names of query and form parameters, as well as the name of the body
|
|
||||||
parameter are sanitized by removing characters that are not allowed in Python
|
|
||||||
symbols. I.e. all characters that are not letters, digits or the underscore are
|
|
||||||
removed, and finally characters are removed from the front until a letter or an
|
|
||||||
under-score is encountered. As an example:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
>>> re.sub('^[^a-zA-Z_]+', '', re.sub('[^0-9a-zA-Z_]', '', '$top'))
|
|
||||||
'top'
|
|
||||||
|
|
||||||
Without this sanitation it would e.g. be impossible to implement an
|
|
||||||
`OData
|
|
||||||
<http://www.odata.org>`_ API.
|
|
||||||
|
|
||||||
You can also convert *CamelCase* parameters to *snake_case* automatically using `pythonic_params` option:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__)
|
|
||||||
app.add_api('api.yaml', ..., pythonic_params=True)
|
|
||||||
|
|
||||||
With this option enabled, Connexion firstly converts *CamelCase* names
|
|
||||||
to *snake_case*. Secondly it looks to see if the name matches a known built-in
|
|
||||||
and if it does it appends an underscore to the name.
|
|
||||||
|
|
||||||
Parameter Variable Converters
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Connexion supports Flask's ``int``, ``float``, and ``path`` route parameter
|
|
||||||
`variable converters
|
|
||||||
<http://flask.pocoo.org/docs/0.12/quickstart/#variable-rules>`_.
|
|
||||||
Specify a route parameter's type as ``integer`` or ``number`` or its type as
|
Specify a route parameter's type as ``integer`` or ``number`` or its type as
|
||||||
``string`` and its format as ``path`` to use these converters. For example:
|
``string`` and its format as ``path`` to use these converters.
|
||||||
|
|
||||||
.. code-block:: yaml
|
Path parameters are passed as arguments to your python function, see :doc:`parameters`.
|
||||||
|
|
||||||
paths:
|
Individual paths
|
||||||
/greeting/{name}:
|
----------------
|
||||||
# ...
|
|
||||||
parameters:
|
You can also add individual paths to your application which are not described in your API
|
||||||
- name: name
|
contract. This can be useful for eg. ``/healthz`` or similar endpoints.
|
||||||
in: path
|
|
||||||
required: true
|
.. code-block:: python
|
||||||
type: string
|
:caption: **api.py**
|
||||||
format: path
|
|
||||||
|
@app.route("/healthz")
|
||||||
|
def healthz():
|
||||||
|
return 200
|
||||||
|
|
||||||
|
# Or as alternative to the decorator
|
||||||
|
app.add_url_rule("/healthz", "healthz", healthz)
|
||||||
|
|
||||||
|
.. tab-set::
|
||||||
|
|
||||||
|
.. tab-item:: AsyncApp
|
||||||
|
:sync: AsyncApp
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the ``route`` and ``add_url_rule`` methods
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. automethod:: connexion.AsyncApp.route
|
||||||
|
:noindex:
|
||||||
|
.. automethod:: connexion.AsyncApp.add_url_rule
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. tab-item:: FlaskApp
|
||||||
|
:sync: FlaskApp
|
||||||
|
|
||||||
|
.. dropdown:: View a detailed reference of the ``route`` and ``add_url_rule`` methods
|
||||||
|
:icon: eye
|
||||||
|
|
||||||
|
.. automethod:: connexion.FlaskApp.route
|
||||||
|
:noindex:
|
||||||
|
.. automethod:: connexion.FlaskApp.add_url_rule
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. tab-item:: ConnexionMiddleware
|
||||||
|
:sync: ConnexionMiddleware
|
||||||
|
|
||||||
|
When using the ``ConnexionMiddleware`` around an ASGI or WSGI application, you can
|
||||||
|
register individual routes on the wrapped application.
|
||||||
|
|
||||||
will create an equivalent Flask route ``/greeting/<path:name>``, allowing
|
|
||||||
requests to include forward slashes in the ``name`` url variable.
|
|
||||||
|
|
||||||
API Versioning and basePath
|
API Versioning and basePath
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Setting a base path is useful for versioned APIs. An example of
|
Setting a base path is useful for versioned APIs. An example of
|
||||||
a base path would be the ``1.0`` in ``http://MYHOST/1.0/hello_world``.
|
a base path would be the ``1.0`` in ``http://{HOST}/1.0/hello_world``.
|
||||||
|
|
||||||
If you are using OpenAPI 3.x.x, you set your base URL path in the
|
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
|
servers block of the specification. You can either specify a full
|
||||||
URL, or just a relative path.
|
URL, or just a relative path.
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
:caption: **openapi.yaml**
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
- url: https://MYHOST/1.0
|
- url: https://{{HOST}}/1.0
|
||||||
description: full url example
|
description: full url example
|
||||||
- url: /1.0
|
- url: /1.0
|
||||||
description: relative path example
|
description: relative path example
|
||||||
@@ -362,10 +465,11 @@ URL, or just a relative path.
|
|||||||
paths:
|
paths:
|
||||||
...
|
...
|
||||||
|
|
||||||
If you are using OpenAPI 2.0, you can define a ``basePath`` on the top level
|
If you are using Swagger 2.0, you can define a ``basePath`` on the top level
|
||||||
of your OpenAPI 2.0 specification.
|
of your Swagger 2.0 specification.
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
:caption: **swagger.yaml**
|
||||||
|
|
||||||
basePath: /1.0
|
basePath: /1.0
|
||||||
|
|
||||||
@@ -376,40 +480,9 @@ If you don't want to include the base path in your specification, you
|
|||||||
can provide it when adding the API to your application:
|
can provide it when adding the API to your application:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
:caption: **app.py**
|
||||||
|
|
||||||
app.add_api('my_api.yaml', base_path='/1.0')
|
app.add_api('my_api.yaml', base_path='/1.0')
|
||||||
|
|
||||||
Swagger UI path
|
.. _operation: https://swagger.io/docs/specification/paths-and-operations/#operations
|
||||||
---------------
|
.. _Path parameters: https://swagger.io/docs/specification/describing-parameters/#path-parameters
|
||||||
|
|
||||||
Swagger UI is available at ``/ui/`` by default.
|
|
||||||
|
|
||||||
You can choose another path through options:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
options = {'swagger_url': '/'}
|
|
||||||
app = connexion.App(__name__, options=options)
|
|
||||||
|
|
||||||
Swagger JSON
|
|
||||||
------------
|
|
||||||
Connexion makes the OpenAPI/Swagger specification in JSON format
|
|
||||||
available from ``swagger.json`` in the base path of the API.
|
|
||||||
|
|
||||||
You can disable the Swagger JSON at the application level:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__, specification_dir='swagger/',
|
|
||||||
swagger_json=False)
|
|
||||||
app.add_api('my_api.yaml')
|
|
||||||
|
|
||||||
You can also disable it at the API level:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
app = connexion.FlaskApp(__name__, specification_dir='swagger/')
|
|
||||||
app.add_api('my_api.yaml', swagger_json=False)
|
|
||||||
|
|
||||||
.. _Operation Object: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#operation-object
|
|
||||||
.. _HTTP Methods work in Flask: http://flask.pocoo.org/docs/1.0/quickstart/#http-methods
|
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ def test_resty_resolve_with_default_module_name_will_resolve_resource_root_as_co
|
|||||||
app_produces=["application/json"],
|
app_produces=["application/json"],
|
||||||
app_consumes=["application/json"],
|
app_consumes=["application/json"],
|
||||||
definitions={},
|
definitions={},
|
||||||
resolver=RestyResolver("fakeapi", "api_list"),
|
resolver=RestyResolver("fakeapi", collection_endpoint_name="api_list"),
|
||||||
)
|
)
|
||||||
assert operation.operation_id == "fakeapi.hello.api_list"
|
assert operation.operation_id == "fakeapi.hello.api_list"
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ def test_resty_resolve_with_default_module_name_will_resolve_resource_root_as_co
|
|||||||
path_parameters=[],
|
path_parameters=[],
|
||||||
operation={},
|
operation={},
|
||||||
components=COMPONENTS,
|
components=COMPONENTS,
|
||||||
resolver=RestyResolver("fakeapi", "api_list"),
|
resolver=RestyResolver("fakeapi", collection_endpoint_name="api_list"),
|
||||||
)
|
)
|
||||||
assert operation.operation_id == "fakeapi.hello.api_list"
|
assert operation.operation_id == "fakeapi.hello.api_list"
|
||||||
|
|
||||||
|
|||||||
@@ -151,20 +151,31 @@ def test_methodview_resolve_with_default_module_name_and_x_router_controller_wil
|
|||||||
assert operation.operation_id == "fakeapi.PetsView.search"
|
assert operation.operation_id == "fakeapi.PetsView.search"
|
||||||
|
|
||||||
|
|
||||||
def test_methodview_resolve_with_default_module_name_will_resolve_resource_root_as_configured(
|
def test_method_resolve_with_default_module_name_will_resolve_resource_root_as_configured():
|
||||||
method_view_resolver,
|
|
||||||
):
|
|
||||||
operation = OpenAPIOperation(
|
operation = OpenAPIOperation(
|
||||||
method="GET",
|
method="GET",
|
||||||
path="/pets",
|
path="/pets",
|
||||||
path_parameters=[],
|
path_parameters=[],
|
||||||
operation={},
|
operation={},
|
||||||
components=COMPONENTS,
|
components=COMPONENTS,
|
||||||
resolver=method_view_resolver("fakeapi", "api_list"),
|
resolver=MethodResolver("fakeapi", collection_endpoint_name="api_list"),
|
||||||
)
|
)
|
||||||
assert operation.operation_id == "fakeapi.PetsView.api_list"
|
assert operation.operation_id == "fakeapi.PetsView.api_list"
|
||||||
|
|
||||||
|
|
||||||
|
def test_methodview_resolve_with_default_module_name_will_resolve_resource_root_as_configured():
|
||||||
|
operation = OpenAPIOperation(
|
||||||
|
method="GET",
|
||||||
|
path="/pets",
|
||||||
|
path_parameters=[],
|
||||||
|
operation={},
|
||||||
|
components=COMPONENTS,
|
||||||
|
resolver=MethodViewResolver("fakeapi", collection_endpoint_name="api_list"),
|
||||||
|
)
|
||||||
|
# The collection_endpoint_name is ignored
|
||||||
|
assert operation.operation_id == "fakeapi.PetsView.search"
|
||||||
|
|
||||||
|
|
||||||
def test_methodview_resolve_with_default_module_name_will_resolve_resource_root_post_as_post(
|
def test_methodview_resolve_with_default_module_name_will_resolve_resource_root_post_as_post(
|
||||||
method_view_resolver,
|
method_view_resolver,
|
||||||
):
|
):
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -4,7 +4,7 @@ rst-roles=class,mod,obj
|
|||||||
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
|
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
|
||||||
# Longest docstring in current code base
|
# Longest docstring in current code base
|
||||||
max-line-length=137
|
max-line-length=137
|
||||||
extend-ignore=E203
|
extend-ignore=E203,RST303
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
isolated_build = True
|
isolated_build = True
|
||||||
|
|||||||
Reference in New Issue
Block a user