2016-10-20 05:07:07 +01:00
|
|
|
from functools import lru_cache
|
2021-02-03 20:36:44 +00:00
|
|
|
from typing import FrozenSet, Iterable, List, Optional, Union
|
2016-12-25 09:43:45 +00:00
|
|
|
|
2021-02-03 22:42:24 +00:00
|
|
|
from sanic_routing import BaseRouter, route
|
2021-02-01 07:56:58 +00:00
|
|
|
from sanic_routing.exceptions import NoMethod
|
|
|
|
from sanic_routing.exceptions import NotFound as RoutingNotFound
|
2021-01-26 07:24:38 +00:00
|
|
|
from sanic_routing.route import Route
|
2016-12-25 09:43:45 +00:00
|
|
|
|
2021-01-26 06:47:16 +00:00
|
|
|
from sanic.constants import HTTP_METHODS
|
2021-02-01 07:56:58 +00:00
|
|
|
from sanic.exceptions import MethodNotSupported, NotFound
|
2021-01-26 06:47:16 +00:00
|
|
|
from sanic.request import Request
|
2016-12-25 09:43:45 +00:00
|
|
|
|
2016-10-15 20:59:00 +01:00
|
|
|
|
2021-01-26 06:47:16 +00:00
|
|
|
class Router(BaseRouter):
|
2021-01-31 14:31:04 +00:00
|
|
|
"""
|
|
|
|
The router implementation responsible for routing a :class:`Request` object
|
|
|
|
to the appropriate handler.
|
|
|
|
"""
|
|
|
|
|
2021-01-26 06:47:16 +00:00
|
|
|
DEFAULT_METHOD = "GET"
|
|
|
|
ALLOWED_METHODS = HTTP_METHODS
|
2018-10-14 01:55:33 +01:00
|
|
|
|
2021-02-01 07:56:58 +00:00
|
|
|
# @lru_cache
|
2021-01-26 06:47:16 +00:00
|
|
|
def get(self, request: Request):
|
2021-01-31 14:31:04 +00:00
|
|
|
"""
|
|
|
|
Retrieve a `Route` object containg the details about how to handle
|
|
|
|
a response for a given request
|
|
|
|
|
|
|
|
:param request: the incoming request object
|
|
|
|
:type request: Request
|
|
|
|
:return: details needed for handling the request and returning the
|
|
|
|
correct response
|
|
|
|
:rtype: Tuple[ RouteHandler, Tuple[Any, ...], Dict[str, Any], str, str,
|
|
|
|
Optional[str], bool, ]
|
|
|
|
"""
|
2021-02-01 07:56:58 +00:00
|
|
|
try:
|
|
|
|
route, handler, params = self.resolve(
|
|
|
|
path=request.path,
|
|
|
|
method=request.method,
|
2021-02-03 20:36:44 +00:00
|
|
|
extra={"host": request.headers.get("host")}
|
2021-02-01 07:56:58 +00:00
|
|
|
)
|
|
|
|
except RoutingNotFound as e:
|
|
|
|
raise NotFound("Requested URL {} not found".format(e.path))
|
|
|
|
except NoMethod as e:
|
|
|
|
raise MethodNotSupported(
|
|
|
|
"Method {} not allowed for URL {}".format(
|
2021-02-03 20:36:44 +00:00
|
|
|
request.method, request.path
|
2021-02-01 07:56:58 +00:00
|
|
|
),
|
|
|
|
method=request.method,
|
|
|
|
allowed_methods=e.allowed_methods,
|
|
|
|
)
|
2017-02-02 17:21:14 +00:00
|
|
|
|
2021-01-26 06:47:16 +00:00
|
|
|
# TODO: Implement response
|
|
|
|
# - args,
|
|
|
|
# - endpoint,
|
2017-02-02 17:21:14 +00:00
|
|
|
|
2021-01-26 07:24:38 +00:00
|
|
|
return (
|
|
|
|
handler,
|
|
|
|
(),
|
|
|
|
params,
|
|
|
|
route.path,
|
|
|
|
route.name,
|
|
|
|
None,
|
|
|
|
route.ctx.ignore_body,
|
|
|
|
)
|
2017-02-02 17:21:14 +00:00
|
|
|
|
2018-10-14 01:55:33 +01:00
|
|
|
def add(
|
|
|
|
self,
|
2021-01-31 14:31:04 +00:00
|
|
|
uri: str,
|
|
|
|
methods: Iterable[str],
|
2018-10-14 01:55:33 +01:00
|
|
|
handler,
|
2021-02-03 20:36:44 +00:00
|
|
|
host: Optional[Union[str, FrozenSet[str]]] = None,
|
2021-01-31 14:31:04 +00:00
|
|
|
strict_slashes: bool = False,
|
|
|
|
stream: bool = False,
|
|
|
|
ignore_body: bool = False,
|
|
|
|
version: Union[str, float, int] = None,
|
|
|
|
name: Optional[str] = None,
|
2021-02-03 20:36:44 +00:00
|
|
|
) -> Union[Route, List[Route]]:
|
2021-01-31 14:31:04 +00:00
|
|
|
"""
|
|
|
|
Add a handler to the router
|
|
|
|
|
|
|
|
:param uri: the path of the route
|
|
|
|
:type uri: str
|
|
|
|
:param methods: the types of HTTP methods that should be attached,
|
|
|
|
example: ``["GET", "POST", "OPTIONS"]``
|
|
|
|
:type methods: Iterable[str]
|
|
|
|
:param handler: the sync or async function to be executed
|
|
|
|
:type handler: RouteHandler
|
|
|
|
:param host: host that the route should be on, defaults to None
|
|
|
|
:type host: Optional[str], optional
|
|
|
|
:param strict_slashes: whether to apply strict slashes, defaults
|
|
|
|
to False
|
|
|
|
:type strict_slashes: bool, optional
|
|
|
|
:param stream: whether to stream the response, defaults to False
|
|
|
|
:type stream: bool, optional
|
|
|
|
:param ignore_body: whether the incoming request body should be read,
|
|
|
|
defaults to False
|
|
|
|
:type ignore_body: bool, optional
|
|
|
|
:param version: a version modifier for the uri, defaults to None
|
|
|
|
:type version: Union[str, float, int], optional
|
|
|
|
:param name: an identifying name of the route, defaults to None
|
|
|
|
:type name: Optional[str], optional
|
|
|
|
:return: the route object
|
|
|
|
:rtype: Route
|
|
|
|
"""
|
2021-01-26 06:47:16 +00:00
|
|
|
# TODO: Implement
|
|
|
|
# - host
|
|
|
|
# - strict_slashes
|
|
|
|
# - ignore_body
|
2021-01-26 21:14:47 +00:00
|
|
|
# - stream
|
2021-01-26 07:24:38 +00:00
|
|
|
if version is not None:
|
|
|
|
version = str(version).strip("/").lstrip("v")
|
|
|
|
uri = "/".join([f"/v{version}", uri.lstrip("/")])
|
|
|
|
|
2021-02-03 20:36:44 +00:00
|
|
|
params = dict(
|
2021-02-01 07:56:58 +00:00
|
|
|
path=uri,
|
|
|
|
handler=handler,
|
|
|
|
methods=methods,
|
|
|
|
name=name,
|
|
|
|
strict=strict_slashes,
|
2021-01-26 07:24:38 +00:00
|
|
|
)
|
|
|
|
|
2021-02-03 20:36:44 +00:00
|
|
|
if isinstance(host, str):
|
|
|
|
hosts = [host]
|
|
|
|
else:
|
|
|
|
hosts = host or [None]
|
|
|
|
|
|
|
|
routes = []
|
|
|
|
|
|
|
|
for host in hosts:
|
|
|
|
if host:
|
|
|
|
params.update({"requirements": {"host": host}})
|
|
|
|
|
|
|
|
route = super().add(**params)
|
|
|
|
route.ctx.ignore_body = ignore_body
|
|
|
|
route.ctx.stream = stream
|
|
|
|
|
|
|
|
routes.append(route)
|
|
|
|
|
|
|
|
if len(routes) == 1:
|
|
|
|
return routes[0]
|
|
|
|
return routes
|
2016-10-15 20:59:00 +01:00
|
|
|
|
2021-02-01 07:56:58 +00:00
|
|
|
def is_stream_handler(self, request) -> bool:
|
2016-10-20 12:33:28 +01:00
|
|
|
"""
|
2021-02-01 07:56:58 +00:00
|
|
|
Handler for request is stream or not.
|
2017-05-05 12:09:32 +01:00
|
|
|
|
|
|
|
:param request: Request object
|
|
|
|
:return: bool
|
|
|
|
"""
|
2017-06-09 16:33:34 +01:00
|
|
|
try:
|
|
|
|
handler = self.get(request)[0]
|
2017-12-14 06:59:02 +00:00
|
|
|
except (NotFound, MethodNotSupported):
|
2017-06-09 16:33:34 +01:00
|
|
|
return False
|
2018-10-14 01:55:33 +01:00
|
|
|
if hasattr(handler, "view_class") and hasattr(
|
|
|
|
handler.view_class, request.method.lower()
|
|
|
|
):
|
2017-05-07 13:11:40 +01:00
|
|
|
handler = getattr(handler.view_class, request.method.lower())
|
2018-10-14 01:55:33 +01:00
|
|
|
return hasattr(handler, "is_stream")
|
2021-02-03 22:42:24 +00:00
|
|
|
|
|
|
|
# @lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
|
|
|
def find_route_by_view_name(self, view_name, name=None):
|
|
|
|
"""
|
|
|
|
Find a route in the router based on the specified view name.
|
|
|
|
|
|
|
|
:param view_name: string of view name to search by
|
|
|
|
:param kwargs: additional params, usually for static files
|
|
|
|
:return: tuple containing (uri, Route)
|
|
|
|
"""
|
|
|
|
if not view_name:
|
|
|
|
return None, None
|
|
|
|
|
|
|
|
if view_name == "static" or view_name.endswith(".static"):
|
|
|
|
looking_for = f"_static_{name}"
|
|
|
|
route = self.name_index.get(looking_for)
|
|
|
|
else:
|
|
|
|
route = self.name_index.get(view_name)
|
|
|
|
|
|
|
|
if not route:
|
|
|
|
return None, None
|
|
|
|
|
|
|
|
return route.path, route
|