Initial introduction of sanic-routing
This commit is contained in:
parent
c32e7fd678
commit
792a72bdf4
16
sanic/app.py
16
sanic/app.py
|
@ -20,7 +20,12 @@ from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.config import BASE_LOGO, Config
|
from sanic.config import BASE_LOGO, Config
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import SanicException, ServerError, URLBuildError
|
from sanic.exceptions import (
|
||||||
|
NotFound,
|
||||||
|
SanicException,
|
||||||
|
ServerError,
|
||||||
|
URLBuildError,
|
||||||
|
)
|
||||||
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
from sanic.handlers import ErrorHandler, ListenerType, MiddlewareType
|
||||||
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
from sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
|
@ -67,7 +72,9 @@ class Sanic:
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.asgi = False
|
self.asgi = False
|
||||||
self.router = router or Router(self)
|
self.router = router or Router(
|
||||||
|
exception=NotFound, method_handler_exception=NotFound
|
||||||
|
)
|
||||||
self.request_class = request_class
|
self.request_class = request_class
|
||||||
self.error_handler = error_handler or ErrorHandler()
|
self.error_handler = error_handler or ErrorHandler()
|
||||||
self.config = Config(load_env=load_env)
|
self.config = Config(load_env=load_env)
|
||||||
|
@ -206,7 +213,6 @@ class Sanic:
|
||||||
if stream:
|
if stream:
|
||||||
handler.is_stream = stream
|
handler.is_stream = stream
|
||||||
|
|
||||||
routes.extend(
|
|
||||||
self.router.add(
|
self.router.add(
|
||||||
uri=uri,
|
uri=uri,
|
||||||
methods=methods,
|
methods=methods,
|
||||||
|
@ -217,7 +223,6 @@ class Sanic:
|
||||||
name=name,
|
name=name,
|
||||||
ignore_body=ignore_body,
|
ignore_body=ignore_body,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
return routes, handler
|
return routes, handler
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -1321,6 +1326,9 @@ class Sanic:
|
||||||
auto_reload=False,
|
auto_reload=False,
|
||||||
):
|
):
|
||||||
"""Helper function used by `run` and `create_server`."""
|
"""Helper function used by `run` and `create_server`."""
|
||||||
|
|
||||||
|
self.router.finalize()
|
||||||
|
|
||||||
if isinstance(ssl, dict):
|
if isinstance(ssl, dict):
|
||||||
# try common aliaseses
|
# try common aliaseses
|
||||||
cert = ssl.get("cert") or ssl.get("certificate")
|
cert = ssl.get("cert") or ssl.get("certificate")
|
||||||
|
|
507
sanic/router.py
507
sanic/router.py
|
@ -1,133 +1,29 @@
|
||||||
import re
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from collections import defaultdict, namedtuple
|
|
||||||
from collections.abc import Iterable
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from urllib.parse import unquote
|
|
||||||
|
|
||||||
from sanic.exceptions import MethodNotSupported, NotFound
|
from sanic_routing import BaseRouter
|
||||||
from sanic.views import CompositionView
|
|
||||||
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.log import logger
|
||||||
|
from sanic.request import Request
|
||||||
|
|
||||||
|
|
||||||
Route = namedtuple(
|
class Router(BaseRouter):
|
||||||
"Route",
|
DEFAULT_METHOD = "GET"
|
||||||
[
|
ALLOWED_METHODS = HTTP_METHODS
|
||||||
"handler",
|
|
||||||
"methods",
|
|
||||||
"pattern",
|
|
||||||
"parameters",
|
|
||||||
"name",
|
|
||||||
"uri",
|
|
||||||
"endpoint",
|
|
||||||
"ignore_body",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
Parameter = namedtuple("Parameter", ["name", "cast"])
|
|
||||||
|
|
||||||
REGEX_TYPES = {
|
@lru_cache
|
||||||
"string": (str, r"[^/]+"),
|
def get(self, request: Request):
|
||||||
"int": (int, r"-?\d+"),
|
route, handler, params = self.resolve(
|
||||||
"number": (float, r"-?(?:\d+(?:\.\d*)?|\.\d+)"),
|
path=request.path,
|
||||||
"alpha": (str, r"[A-Za-z]+"),
|
method=request.method,
|
||||||
"path": (str, r"[^/].*?"),
|
|
||||||
"uuid": (
|
|
||||||
uuid.UUID,
|
|
||||||
r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-"
|
|
||||||
r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
ROUTER_CACHE_SIZE = 1024
|
|
||||||
|
|
||||||
|
|
||||||
def url_hash(url):
|
|
||||||
return url.count("/")
|
|
||||||
|
|
||||||
|
|
||||||
class RouteExists(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RouteDoesNotExist(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ParameterNameConflicts(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
|
||||||
"""Router supports basic routing with parameters and method checks
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@sanic.route('/my/url/<my_param>', methods=['GET', 'POST', ...])
|
|
||||||
def my_route(request, my_param):
|
|
||||||
do stuff...
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@sanic.route('/my/url/<my_param:my_type>', methods['GET', 'POST', ...])
|
|
||||||
def my_route_with_type(request, my_param: my_type):
|
|
||||||
do stuff...
|
|
||||||
|
|
||||||
Parameters will be passed as keyword arguments to the request handling
|
|
||||||
function. Provided parameters can also have a type by appending :type to
|
|
||||||
the <parameter>. Given parameter must be able to be type-casted to this.
|
|
||||||
If no type is provided, a string is expected. A regular expression can
|
|
||||||
also be passed in as the type. The argument given to the function will
|
|
||||||
always be a string, independent of the type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
routes_static = None
|
|
||||||
routes_dynamic = None
|
|
||||||
routes_always_check = None
|
|
||||||
parameter_pattern = re.compile(r"<(.+?)>")
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
self.routes_all = {}
|
|
||||||
self.routes_names = {}
|
|
||||||
self.routes_static_files = {}
|
|
||||||
self.routes_static = {}
|
|
||||||
self.routes_dynamic = defaultdict(list)
|
|
||||||
self.routes_always_check = []
|
|
||||||
self.hosts = set()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse_parameter_string(cls, parameter_string):
|
|
||||||
"""Parse a parameter string into its constituent name, type, and
|
|
||||||
pattern
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
parse_parameter_string('<param_one:[A-z]>')` ->
|
|
||||||
('param_one', str, '[A-z]')
|
|
||||||
|
|
||||||
:param parameter_string: String to parse
|
|
||||||
:return: tuple containing
|
|
||||||
(parameter_name, parameter_type, parameter_pattern)
|
|
||||||
"""
|
|
||||||
# We could receive NAME or NAME:PATTERN
|
|
||||||
name = parameter_string
|
|
||||||
pattern = "string"
|
|
||||||
if ":" in parameter_string:
|
|
||||||
name, pattern = parameter_string.split(":", 1)
|
|
||||||
if not name:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid parameter syntax: {parameter_string}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
default = (str, pattern)
|
# TODO: Implement response
|
||||||
# Pull from pre-configured types
|
# - args,
|
||||||
_type, pattern = REGEX_TYPES.get(pattern, default)
|
# - endpoint,
|
||||||
|
# - ignore_body,
|
||||||
|
|
||||||
return name, _type, pattern
|
return handler, (), params, route.path, route.name, None, False
|
||||||
|
|
||||||
def add(
|
def add(
|
||||||
self,
|
self,
|
||||||
|
@ -140,364 +36,9 @@ class Router:
|
||||||
version=None,
|
version=None,
|
||||||
name=None,
|
name=None,
|
||||||
):
|
):
|
||||||
"""Add a handler to the route list
|
# TODO: Implement
|
||||||
|
# - host
|
||||||
:param uri: path to match
|
# - strict_slashes
|
||||||
:param methods: sequence of accepted method names. If none are
|
# - version
|
||||||
provided, any method is allowed
|
# - ignore_body
|
||||||
:param handler: request handler function.
|
super().add(path=uri, handler=handler, methods=methods, name=name)
|
||||||
When executed, it should provide a response object.
|
|
||||||
:param strict_slashes: strict to trailing slash
|
|
||||||
:param ignore_body: Handler should not read the body, if any
|
|
||||||
:param version: current version of the route or blueprint. See
|
|
||||||
docs for further details.
|
|
||||||
:return: Nothing
|
|
||||||
"""
|
|
||||||
routes = []
|
|
||||||
if version is not None:
|
|
||||||
version = re.escape(str(version).strip("/").lstrip("v"))
|
|
||||||
uri = "/".join([f"/v{version}", uri.lstrip("/")])
|
|
||||||
# add regular version
|
|
||||||
routes.append(
|
|
||||||
self._add(uri, methods, handler, host, name, ignore_body)
|
|
||||||
)
|
|
||||||
|
|
||||||
if strict_slashes:
|
|
||||||
return routes
|
|
||||||
|
|
||||||
if not isinstance(host, str) and host is not None:
|
|
||||||
# we have gotten back to the top of the recursion tree where the
|
|
||||||
# host was originally a list. By now, we've processed the strict
|
|
||||||
# slashes logic on the leaf nodes (the individual host strings in
|
|
||||||
# the list of host)
|
|
||||||
return routes
|
|
||||||
|
|
||||||
# Add versions with and without trailing /
|
|
||||||
slashed_methods = self.routes_all.get(uri + "/", frozenset({}))
|
|
||||||
unslashed_methods = self.routes_all.get(uri[:-1], frozenset({}))
|
|
||||||
if isinstance(methods, Iterable):
|
|
||||||
_slash_is_missing = all(
|
|
||||||
method in slashed_methods for method in methods
|
|
||||||
)
|
|
||||||
_without_slash_is_missing = all(
|
|
||||||
method in unslashed_methods for method in methods
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_slash_is_missing = methods in slashed_methods
|
|
||||||
_without_slash_is_missing = methods in unslashed_methods
|
|
||||||
|
|
||||||
slash_is_missing = not uri[-1] == "/" and not _slash_is_missing
|
|
||||||
without_slash_is_missing = (
|
|
||||||
uri[-1] == "/" and not _without_slash_is_missing and not uri == "/"
|
|
||||||
)
|
|
||||||
# add version with trailing slash
|
|
||||||
if slash_is_missing:
|
|
||||||
routes.append(
|
|
||||||
self._add(uri + "/", methods, handler, host, name, ignore_body)
|
|
||||||
)
|
|
||||||
# add version without trailing slash
|
|
||||||
elif without_slash_is_missing:
|
|
||||||
routes.append(
|
|
||||||
self._add(uri[:-1], methods, handler, host, name, ignore_body)
|
|
||||||
)
|
|
||||||
|
|
||||||
return routes
|
|
||||||
|
|
||||||
def _add(
|
|
||||||
self, uri, methods, handler, host=None, name=None, ignore_body=False
|
|
||||||
):
|
|
||||||
"""Add a handler to the route list
|
|
||||||
|
|
||||||
:param uri: path to match
|
|
||||||
:param methods: sequence of accepted method names. If none are
|
|
||||||
provided, any method is allowed
|
|
||||||
:param handler: request handler function.
|
|
||||||
When executed, it should provide a response object.
|
|
||||||
:param name: user defined route name for url_for
|
|
||||||
:return: Nothing
|
|
||||||
"""
|
|
||||||
if host is not None:
|
|
||||||
if isinstance(host, str):
|
|
||||||
uri = host + uri
|
|
||||||
self.hosts.add(host)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not isinstance(host, Iterable):
|
|
||||||
raise ValueError(
|
|
||||||
f"Expected either string or Iterable of "
|
|
||||||
f"host strings, not {host!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for host_ in host:
|
|
||||||
self.add(uri, methods, handler, host_, name)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Dict for faster lookups of if method allowed
|
|
||||||
if methods:
|
|
||||||
methods = frozenset(methods)
|
|
||||||
|
|
||||||
parameters = []
|
|
||||||
parameter_names = set()
|
|
||||||
properties = {"unhashable": None}
|
|
||||||
|
|
||||||
def add_parameter(match):
|
|
||||||
name = match.group(1)
|
|
||||||
name, _type, pattern = self.parse_parameter_string(name)
|
|
||||||
|
|
||||||
if name in parameter_names:
|
|
||||||
raise ParameterNameConflicts(
|
|
||||||
f"Multiple parameter named <{name}> " f"in route uri {uri}"
|
|
||||||
)
|
|
||||||
parameter_names.add(name)
|
|
||||||
|
|
||||||
parameter = Parameter(name=name, cast=_type)
|
|
||||||
parameters.append(parameter)
|
|
||||||
|
|
||||||
# Mark the whole route as unhashable if it has the hash key in it
|
|
||||||
if re.search(r"(^|[^^]){1}/", pattern):
|
|
||||||
properties["unhashable"] = True
|
|
||||||
# Mark the route as unhashable if it matches the hash key
|
|
||||||
elif re.search(r"/", pattern):
|
|
||||||
properties["unhashable"] = True
|
|
||||||
|
|
||||||
return f"({pattern})"
|
|
||||||
|
|
||||||
pattern_string = re.sub(self.parameter_pattern, add_parameter, uri)
|
|
||||||
pattern = re.compile(fr"^{pattern_string}$")
|
|
||||||
|
|
||||||
def merge_route(route, methods, handler):
|
|
||||||
# merge to the existing route when possible.
|
|
||||||
if not route.methods or not methods:
|
|
||||||
# method-unspecified routes are not mergeable.
|
|
||||||
raise RouteExists(f"Route already registered: {uri}")
|
|
||||||
elif route.methods.intersection(methods):
|
|
||||||
# already existing method is not overloadable.
|
|
||||||
duplicated = methods.intersection(route.methods)
|
|
||||||
duplicated_methods = ",".join(list(duplicated))
|
|
||||||
|
|
||||||
raise RouteExists(
|
|
||||||
f"Route already registered: {uri} [{duplicated_methods}]"
|
|
||||||
)
|
|
||||||
if isinstance(route.handler, CompositionView):
|
|
||||||
view = route.handler
|
|
||||||
else:
|
|
||||||
view = CompositionView()
|
|
||||||
view.add(route.methods, route.handler)
|
|
||||||
view.add(methods, handler)
|
|
||||||
route = route._replace(
|
|
||||||
handler=view, methods=methods.union(route.methods)
|
|
||||||
)
|
|
||||||
return route
|
|
||||||
|
|
||||||
if parameters:
|
|
||||||
# TODO: This is too complex, we need to reduce the complexity
|
|
||||||
if properties["unhashable"]:
|
|
||||||
routes_to_check = self.routes_always_check
|
|
||||||
ndx, route = self.check_dynamic_route_exists(
|
|
||||||
pattern, routes_to_check, parameters
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
routes_to_check = self.routes_dynamic[url_hash(uri)]
|
|
||||||
ndx, route = self.check_dynamic_route_exists(
|
|
||||||
pattern, routes_to_check, parameters
|
|
||||||
)
|
|
||||||
if ndx != -1:
|
|
||||||
# Pop the ndx of the route, no dups of the same route
|
|
||||||
routes_to_check.pop(ndx)
|
|
||||||
else:
|
|
||||||
route = self.routes_all.get(uri)
|
|
||||||
|
|
||||||
# prefix the handler name with the blueprint name
|
|
||||||
# if available
|
|
||||||
# special prefix for static files
|
|
||||||
is_static = False
|
|
||||||
if name and name.startswith("_static_"):
|
|
||||||
is_static = True
|
|
||||||
name = name.split("_static_", 1)[-1]
|
|
||||||
|
|
||||||
if hasattr(handler, "__blueprintname__"):
|
|
||||||
bp_name = handler.__blueprintname__
|
|
||||||
|
|
||||||
handler_name = f"{bp_name}.{name or handler.__name__}"
|
|
||||||
else:
|
|
||||||
handler_name = name or getattr(
|
|
||||||
handler, "__name__", handler.__class__.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
if route:
|
|
||||||
route = merge_route(route, methods, handler)
|
|
||||||
else:
|
|
||||||
endpoint = self.app._build_endpoint_name(handler_name)
|
|
||||||
|
|
||||||
route = Route(
|
|
||||||
handler=handler,
|
|
||||||
methods=methods,
|
|
||||||
pattern=pattern,
|
|
||||||
parameters=parameters,
|
|
||||||
name=handler_name,
|
|
||||||
uri=uri,
|
|
||||||
endpoint=endpoint,
|
|
||||||
ignore_body=ignore_body,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.routes_all[uri] = route
|
|
||||||
if is_static:
|
|
||||||
pair = self.routes_static_files.get(handler_name)
|
|
||||||
if not (pair and (pair[0] + "/" == uri or uri + "/" == pair[0])):
|
|
||||||
self.routes_static_files[handler_name] = (uri, route)
|
|
||||||
|
|
||||||
else:
|
|
||||||
pair = self.routes_names.get(handler_name)
|
|
||||||
if not (pair and (pair[0] + "/" == uri or uri + "/" == pair[0])):
|
|
||||||
self.routes_names[handler_name] = (uri, route)
|
|
||||||
|
|
||||||
if properties["unhashable"]:
|
|
||||||
self.routes_always_check.append(route)
|
|
||||||
elif parameters:
|
|
||||||
self.routes_dynamic[url_hash(uri)].append(route)
|
|
||||||
else:
|
|
||||||
self.routes_static[uri] = route
|
|
||||||
return route
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_dynamic_route_exists(pattern, routes_to_check, parameters):
|
|
||||||
"""
|
|
||||||
Check if a URL pattern exists in a list of routes provided based on
|
|
||||||
the comparison of URL pattern and the parameters.
|
|
||||||
|
|
||||||
:param pattern: URL parameter pattern
|
|
||||||
:param routes_to_check: list of dynamic routes either hashable or
|
|
||||||
unhashable routes.
|
|
||||||
:param parameters: List of :class:`Parameter` items
|
|
||||||
:return: Tuple of index and route if matching route exists else
|
|
||||||
-1 for index and None for route
|
|
||||||
"""
|
|
||||||
for ndx, route in enumerate(routes_to_check):
|
|
||||||
if route.pattern == pattern and route.parameters == parameters:
|
|
||||||
return ndx, route
|
|
||||||
else:
|
|
||||||
return -1, None
|
|
||||||
|
|
||||||
@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"):
|
|
||||||
return self.routes_static_files.get(name, (None, None))
|
|
||||||
|
|
||||||
return self.routes_names.get(view_name, (None, None))
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
"""Get a request handler based on the URL of the request, or raises an
|
|
||||||
error
|
|
||||||
|
|
||||||
:param request: Request object
|
|
||||||
:return: handler, arguments, keyword arguments
|
|
||||||
"""
|
|
||||||
# No virtual hosts specified; default behavior
|
|
||||||
if not self.hosts:
|
|
||||||
return self._get(request.path, request.method, "")
|
|
||||||
# virtual hosts specified; try to match route to the host header
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self._get(
|
|
||||||
request.path, request.method, request.headers.get("Host", "")
|
|
||||||
)
|
|
||||||
# try default hosts
|
|
||||||
except NotFound:
|
|
||||||
return self._get(request.path, request.method, "")
|
|
||||||
|
|
||||||
def get_supported_methods(self, url):
|
|
||||||
"""Get a list of supported methods for a url and optional host.
|
|
||||||
|
|
||||||
:param url: URL string (including host)
|
|
||||||
:return: frozenset of supported methods
|
|
||||||
"""
|
|
||||||
route = self.routes_all.get(url)
|
|
||||||
# if methods are None then this logic will prevent an error
|
|
||||||
return getattr(route, "methods", None) or frozenset()
|
|
||||||
|
|
||||||
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
|
||||||
def _get(self, url, method, host):
|
|
||||||
"""Get a request handler based on the URL of the request, or raises an
|
|
||||||
error. Internal method for caching.
|
|
||||||
|
|
||||||
:param url: request URL
|
|
||||||
:param method: request method
|
|
||||||
:return: handler, arguments, keyword arguments
|
|
||||||
"""
|
|
||||||
url = unquote(host + url)
|
|
||||||
# Check against known static routes
|
|
||||||
route = self.routes_static.get(url)
|
|
||||||
method_not_supported = MethodNotSupported(
|
|
||||||
f"Method {method} not allowed for URL {url}",
|
|
||||||
method=method,
|
|
||||||
allowed_methods=self.get_supported_methods(url),
|
|
||||||
)
|
|
||||||
|
|
||||||
if route:
|
|
||||||
if route.methods and method not in route.methods:
|
|
||||||
raise method_not_supported
|
|
||||||
match = route.pattern.match(url)
|
|
||||||
else:
|
|
||||||
route_found = False
|
|
||||||
# Move on to testing all regex routes
|
|
||||||
for route in self.routes_dynamic[url_hash(url)]:
|
|
||||||
match = route.pattern.match(url)
|
|
||||||
route_found |= match is not None
|
|
||||||
# Do early method checking
|
|
||||||
if match and method in route.methods:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Lastly, check against all regex routes that cannot be hashed
|
|
||||||
for route in self.routes_always_check:
|
|
||||||
match = route.pattern.match(url)
|
|
||||||
route_found |= match is not None
|
|
||||||
# Do early method checking
|
|
||||||
if match and method in route.methods:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Route was found but the methods didn't match
|
|
||||||
if route_found:
|
|
||||||
raise method_not_supported
|
|
||||||
raise NotFound(f"Requested URL {url} not found")
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
p.name: p.cast(value)
|
|
||||||
for value, p in zip(match.groups(1), route.parameters)
|
|
||||||
}
|
|
||||||
route_handler = route.handler
|
|
||||||
if hasattr(route_handler, "handlers"):
|
|
||||||
route_handler = route_handler.handlers[method]
|
|
||||||
|
|
||||||
return (
|
|
||||||
route_handler,
|
|
||||||
[],
|
|
||||||
kwargs,
|
|
||||||
route.uri,
|
|
||||||
route.name,
|
|
||||||
route.endpoint,
|
|
||||||
route.ignore_body,
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_stream_handler(self, request):
|
|
||||||
"""Handler for request is stream or not.
|
|
||||||
:param request: Request object
|
|
||||||
:return: bool
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
handler = self.get(request)[0]
|
|
||||||
except (NotFound, MethodNotSupported):
|
|
||||||
return False
|
|
||||||
if hasattr(handler, "view_class") and hasattr(
|
|
||||||
handler.view_class, request.method.lower()
|
|
||||||
):
|
|
||||||
handler = getattr(handler.view_class, request.method.lower())
|
|
||||||
return hasattr(handler, "is_stream")
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user