sanic/sanic/router.py

183 lines
5.7 KiB
Python
Raw Normal View History

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
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
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-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:
"""
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()
):
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