Skip to content

Commit

Permalink
Update routing documentation (#1738)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RobbeSneyders authored Oct 11, 2023
1 parent 382fbca commit abc1da7
Show file tree
Hide file tree
Showing 11 changed files with 455 additions and 313 deletions.
4 changes: 2 additions & 2 deletions connexion/apps/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def index():
: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.
: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
methods this rule should be limited to (`GET`, `POST` etc.). By default a rule just
listens for `GET` (and implicitly `HEAD`).
Expand All @@ -231,7 +231,7 @@ def index():
return 'Hello World'
: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
list of methods this rule should be limited to (`GET`, `POST` etc.).
By default a rule just listens for `GET` (and implicitly `HEAD`).
Expand Down
28 changes: 26 additions & 2 deletions connexion/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,41 @@ def context(self) -> t.Dict[str, t.Any]:

@property
def content_type(self) -> str:
"""The content type included in the request headers."""
raise NotImplementedError

@property
def mimetype(self) -> str:
"""The content type included in the request headers stripped from any optional character
set encoding"""
raise NotImplementedError

@property
def path_params(self) -> t.Dict[str, t.Any]:
"""Path parameters exposed as a dictionary"""
raise NotImplementedError

@property
def query_params(self) -> t.Dict[str, t.Any]:
"""Query parameters exposed as a dictionary"""
raise NotImplementedError

def form(self) -> t.Union[t.Dict[str, t.Any], t.Awaitable[t.Dict[str, t.Any]]]:
"""Form data, including files."""
raise NotImplementedError

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

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


Expand Down Expand Up @@ -98,8 +112,10 @@ def form(self):
def files(self):
return self._werkzeug_request.files.to_dict(flat=False)

def json(self):
return self.get_json(silent=True)

def get_body(self):
"""Get body based on content type"""
if self._body is None:
if is_json_mimetype(self.content_type):
self._body = self.get_json(silent=True)
Expand All @@ -115,7 +131,15 @@ def __getattr__(self, item):


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):
self._starlette_request = StarletteRequest(*args, **kwargs)
Expand Down
77 changes: 46 additions & 31 deletions connexion/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,12 @@ class RestyResolver(Resolver):
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
:type default_module_name: str
:param collection_endpoint_name: Name of function to resolve collection endpoints to
"""
super().__init__()
self.default_module_name = default_module_name
Expand Down Expand Up @@ -185,18 +187,23 @@ def get_function_name():

class MethodResolverBase(RestyResolver):
"""
Resolves endpoint functions based on Flask's MethodView semantics, e.g. ::
paths:
/foo_bar:
get:
# Implied function call: api.FooBarView().get
class FooBarView(MethodView):
def get(self):
return ...
def post(self):
return ...
Resolves endpoint functions based on Flask's MethodView semantics, e.g.
.. code-block:: yaml
paths:
/foo_bar:
get:
# Implied function call: api.FooBarView().get
.. code-block:: python
class FooBarView(MethodView):
def get(self):
return ...
def post(self):
return ...
"""

_class_arguments_type = t.Dict[
Expand All @@ -205,24 +212,22 @@ def post(self):

def __init__(self, *args, class_arguments: _class_arguments_type = None, **kwargs):
"""
:param class_arguments: Arguments to instantiate the View Class in the format # noqa
{
"ViewName": {
"args": (positional arguments,)
"kwargs": {
"keyword": "argument"
}
}
}
:param args: Arguments passed to :class:`~RestyResolver`
:param class_arguments: Arguments to instantiate the View Class in the format below
:param kwargs: Keywords arguments passed to :class:`~RestyResolver`
.. code-block:: python
{
"ViewName": {
"args": (positional arguments,)
"kwargs": {
"keyword": "argument"
}
}
}
"""
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)
self.initialized_views: list = []

Expand Down Expand Up @@ -280,7 +285,7 @@ def resolve_method_from_class(self, view_name, meth_name, view_cls):

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

Expand Down Expand Up @@ -310,6 +315,16 @@ class MethodViewResolver(MethodResolverBase):
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):
view = None
for v in self.initialized_views:
Expand Down
4 changes: 4 additions & 0 deletions docs/_static/css/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.rst-content .code-block-caption {
text-align: left;
padding: 0px, 0px, 5px, 5px;
}
17 changes: 16 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@
html_theme = "sphinx_rtd_theme"

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

Expand Down Expand Up @@ -141,7 +152,11 @@
# 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,
# 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
# .htaccess) here, relative to this directory. These files are copied
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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
Connexion will guarantee that it works as you specified.

Expand Down
Loading

0 comments on commit abc1da7

Please sign in to comment.