Add route context (#2302)
This commit is contained in:
@@ -26,12 +26,21 @@ from sanic.log import error_logger
|
||||
from sanic.models.futures import FutureRoute, FutureStatic
|
||||
from sanic.models.handler_types import RouteHandler
|
||||
from sanic.response import HTTPResponse, file, file_stream
|
||||
from sanic.types import HashableDict
|
||||
from sanic.views import CompositionView
|
||||
|
||||
|
||||
RouteWrapper = Callable[
|
||||
[RouteHandler], Union[RouteHandler, Tuple[Route, RouteHandler]]
|
||||
]
|
||||
RESTRICTED_ROUTE_CONTEXT = (
|
||||
"ignore_body",
|
||||
"stream",
|
||||
"hosts",
|
||||
"static",
|
||||
"error_format",
|
||||
"websocket",
|
||||
)
|
||||
|
||||
|
||||
class RouteMixin:
|
||||
@@ -65,10 +74,20 @@ class RouteMixin:
|
||||
static: bool = False,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs: Any,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Decorate a function to be registered as a route
|
||||
|
||||
|
||||
**Example using context kwargs**
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.route(..., ctx_foo="foobar")
|
||||
async def route_handler(request: Request):
|
||||
assert request.route.ctx.foo == "foobar"
|
||||
|
||||
:param uri: path of the URL
|
||||
:param methods: list or tuple of methods allowed
|
||||
:param host: the host, if required
|
||||
@@ -80,6 +99,8 @@ class RouteMixin:
|
||||
body (eg. GET requests)
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: tuple of routes, decorated function
|
||||
"""
|
||||
|
||||
@@ -94,6 +115,8 @@ class RouteMixin:
|
||||
if not methods and not websocket:
|
||||
methods = frozenset({"GET"})
|
||||
|
||||
route_context = self._build_route_context(ctx_kwargs)
|
||||
|
||||
def decorator(handler):
|
||||
nonlocal uri
|
||||
nonlocal methods
|
||||
@@ -152,6 +175,7 @@ class RouteMixin:
|
||||
static,
|
||||
version_prefix,
|
||||
error_format,
|
||||
route_context,
|
||||
)
|
||||
|
||||
self._future_routes.add(route)
|
||||
@@ -196,6 +220,7 @@ class RouteMixin:
|
||||
stream: bool = False,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteHandler:
|
||||
"""A helper method to register class instance or
|
||||
functions as a handler to the application url
|
||||
@@ -212,6 +237,8 @@ class RouteMixin:
|
||||
:param stream: boolean specifying if the handler is a stream handler
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: function or class instance
|
||||
"""
|
||||
# Handle HTTPMethodView differently
|
||||
@@ -247,6 +274,7 @@ class RouteMixin:
|
||||
name=name,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)(handler)
|
||||
return handler
|
||||
|
||||
@@ -261,6 +289,7 @@ class RouteMixin:
|
||||
ignore_body: bool = True,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **GET** *HTTP* method
|
||||
@@ -273,6 +302,8 @@ class RouteMixin:
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -285,6 +316,7 @@ class RouteMixin:
|
||||
ignore_body=ignore_body,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def post(
|
||||
@@ -297,6 +329,7 @@ class RouteMixin:
|
||||
name: Optional[str] = None,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **POST** *HTTP* method
|
||||
@@ -309,6 +342,8 @@ class RouteMixin:
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -321,6 +356,7 @@ class RouteMixin:
|
||||
name=name,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def put(
|
||||
@@ -333,6 +369,7 @@ class RouteMixin:
|
||||
name: Optional[str] = None,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **PUT** *HTTP* method
|
||||
@@ -345,6 +382,8 @@ class RouteMixin:
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -357,6 +396,7 @@ class RouteMixin:
|
||||
name=name,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def head(
|
||||
@@ -369,6 +409,7 @@ class RouteMixin:
|
||||
ignore_body: bool = True,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **HEAD** *HTTP* method
|
||||
@@ -389,6 +430,8 @@ class RouteMixin:
|
||||
:type ignore_body: bool, optional
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -401,6 +444,7 @@ class RouteMixin:
|
||||
ignore_body=ignore_body,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def options(
|
||||
@@ -413,6 +457,7 @@ class RouteMixin:
|
||||
ignore_body: bool = True,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **OPTIONS** *HTTP* method
|
||||
@@ -433,6 +478,8 @@ class RouteMixin:
|
||||
:type ignore_body: bool, optional
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -445,6 +492,7 @@ class RouteMixin:
|
||||
ignore_body=ignore_body,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def patch(
|
||||
@@ -457,6 +505,7 @@ class RouteMixin:
|
||||
name: Optional[str] = None,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **PATCH** *HTTP* method
|
||||
@@ -479,6 +528,8 @@ class RouteMixin:
|
||||
:type ignore_body: bool, optional
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -491,6 +542,7 @@ class RouteMixin:
|
||||
name=name,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def delete(
|
||||
@@ -503,6 +555,7 @@ class RouteMixin:
|
||||
ignore_body: bool = True,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
) -> RouteWrapper:
|
||||
"""
|
||||
Add an API URL under the **DELETE** *HTTP* method
|
||||
@@ -515,6 +568,8 @@ class RouteMixin:
|
||||
:param name: Unique name that can be used to identify the Route
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Object decorated with :func:`route` method
|
||||
"""
|
||||
return self.route(
|
||||
@@ -527,6 +582,7 @@ class RouteMixin:
|
||||
ignore_body=ignore_body,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def websocket(
|
||||
@@ -540,6 +596,7 @@ class RouteMixin:
|
||||
apply: bool = True,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
):
|
||||
"""
|
||||
Decorate a function to be registered as a websocket route
|
||||
@@ -553,6 +610,8 @@ class RouteMixin:
|
||||
be used with :func:`url_for`
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: tuple of routes, decorated function
|
||||
"""
|
||||
return self.route(
|
||||
@@ -567,6 +626,7 @@ class RouteMixin:
|
||||
websocket=True,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)
|
||||
|
||||
def add_websocket_route(
|
||||
@@ -580,6 +640,7 @@ class RouteMixin:
|
||||
name: Optional[str] = None,
|
||||
version_prefix: str = "/v",
|
||||
error_format: Optional[str] = None,
|
||||
**ctx_kwargs,
|
||||
):
|
||||
"""
|
||||
A helper method to register a function as a websocket route.
|
||||
@@ -598,6 +659,8 @@ class RouteMixin:
|
||||
be used with :func:`url_for`
|
||||
:param version_prefix: URL path that should be before the version
|
||||
value; default: ``/v``
|
||||
:param ctx_kwargs: Keyword arguments that begin with a ctx_* prefix
|
||||
will be appended to the route context (``route.ctx``)
|
||||
:return: Objected decorated by :func:`websocket`
|
||||
"""
|
||||
return self.websocket(
|
||||
@@ -609,6 +672,7 @@ class RouteMixin:
|
||||
name=name,
|
||||
version_prefix=version_prefix,
|
||||
error_format=error_format,
|
||||
**ctx_kwargs,
|
||||
)(handler)
|
||||
|
||||
def static(
|
||||
@@ -957,3 +1021,28 @@ class RouteMixin:
|
||||
HttpResponseVisitor().visit(node)
|
||||
|
||||
return types
|
||||
|
||||
def _build_route_context(self, raw):
|
||||
ctx_kwargs = {
|
||||
key.replace("ctx_", ""): raw.pop(key)
|
||||
for key in {**raw}.keys()
|
||||
if key.startswith("ctx_")
|
||||
}
|
||||
restricted = [
|
||||
key for key in ctx_kwargs.keys() if key in RESTRICTED_ROUTE_CONTEXT
|
||||
]
|
||||
if restricted:
|
||||
restricted_arguments = ", ".join(restricted)
|
||||
raise AttributeError(
|
||||
"Cannot use restricted route context: "
|
||||
f"{restricted_arguments}. This limitation is only in place "
|
||||
"until v22.3 when the restricted names will no longer be in"
|
||||
"conflict. See https://github.com/sanic-org/sanic/issues/2303 "
|
||||
"for more information."
|
||||
)
|
||||
if raw:
|
||||
unexpected_arguments = ", ".join(raw.keys())
|
||||
raise TypeError(
|
||||
f"Unexpected keyword arguments: {unexpected_arguments}"
|
||||
)
|
||||
return HashableDict(ctx_kwargs)
|
||||
|
||||
@@ -4,11 +4,7 @@ from typing import Any, Callable, Dict, Optional, Set, Union
|
||||
from sanic.models.futures import FutureSignal
|
||||
from sanic.models.handler_types import SignalHandler
|
||||
from sanic.signals import Signal
|
||||
|
||||
|
||||
class HashableDict(dict):
|
||||
def __hash__(self):
|
||||
return hash(tuple(sorted(self.items())))
|
||||
from sanic.types import HashableDict
|
||||
|
||||
|
||||
class SignalMixin:
|
||||
|
||||
Reference in New Issue
Block a user