diff --git a/sanic/app.py b/sanic/app.py index 84744a4d..b0041507 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -16,7 +16,7 @@ from urllib.parse import urlencode, urlunparse from sanic_routing.route import Route -from sanic import reloader_helpers +from sanic import reloader_helpers, websocket from sanic.asgi import ASGIApp from sanic.base import BaseSanic from sanic.blueprint_group import BlueprintGroup @@ -224,7 +224,26 @@ class Sanic(BaseSanic): return self.register_listener(listener.listener, listener.event) def _apply_route(self, route: FutureRoute) -> Route: - return self.router.add(**route._asdict()) + # TODO: + # - move websocket handler out and attach it when applying + params = route._asdict() + websocket = params.pop("websocket", False) + subprotocols = params.pop("subprotocols", None) + + + if websocket: + self.enable_websocket() + websocket_handler = partial( + self._websocket_handler, + route.handler, + subprotocols=subprotocols, + ) + websocket_handler.__name__ = ( + "websocket_handler_" + route.handler.__name__ + ) + websocket_handler.is_websocket = True + params["handler"] = websocket_handler + return self.router.add(**params) def _apply_static(self, static: FutureStatic) -> Route: return static_register(self, static) @@ -339,7 +358,7 @@ class Sanic(BaseSanic): out = uri # find all the parameters we will need to build in the URL - matched_params = re.findall(self.router.parameter_pattern, uri) + # matched_params = re.findall(self.router.parameter_pattern, uri) # _method is only a placeholder now, don't know how to support it kwargs.pop("_method", None) @@ -364,45 +383,45 @@ class Sanic(BaseSanic): if "://" in netloc[:8]: netloc = netloc.split("://", 1)[-1] - for match in matched_params: - name, _type, pattern = self.router.parse_parameter_string(match) - # we only want to match against each individual parameter - specific_pattern = f"^{pattern}$" - supplied_param = None + # for match in matched_params: + # name, _type, pattern = self.router.parse_parameter_string(match) + # # we only want to match against each individual parameter + # specific_pattern = f"^{pattern}$" + # supplied_param = None - if name in kwargs: - supplied_param = kwargs.get(name) - del kwargs[name] - else: - raise URLBuildError( - f"Required parameter `{name}` was not passed to url_for" - ) + # if name in kwargs: + # supplied_param = kwargs.get(name) + # del kwargs[name] + # else: + # raise URLBuildError( + # f"Required parameter `{name}` was not passed to url_for" + # ) - supplied_param = str(supplied_param) - # determine if the parameter supplied by the caller passes the test - # in the URL - passes_pattern = re.match(specific_pattern, supplied_param) + # supplied_param = str(supplied_param) + # # determine if the parameter supplied by the caller passes the test + # # in the URL + # passes_pattern = re.match(specific_pattern, supplied_param) - if not passes_pattern: - if _type != str: - type_name = _type.__name__ + # if not passes_pattern: + # if _type != str: + # type_name = _type.__name__ - msg = ( - f'Value "{supplied_param}" ' - f"for parameter `{name}` does not " - f"match pattern for type `{type_name}`: {pattern}" - ) - else: - msg = ( - f'Value "{supplied_param}" for parameter `{name}` ' - f"does not satisfy pattern {pattern}" - ) - raise URLBuildError(msg) + # msg = ( + # f'Value "{supplied_param}" ' + # f"for parameter `{name}` does not " + # f"match pattern for type `{type_name}`: {pattern}" + # ) + # else: + # msg = ( + # f'Value "{supplied_param}" for parameter `{name}` ' + # f"does not satisfy pattern {pattern}" + # ) + # raise URLBuildError(msg) - # replace the parameter in the URL with the supplied value - replacement_regex = f"(<{name}.*?>)" + # # replace the parameter in the URL with the supplied value + # replacement_regex = f"(<{name}.*?>)" - out = re.sub(replacement_regex, supplied_param, out) + # out = re.sub(replacement_regex, supplied_param, out) # parse the remainder of the keyword arguments into a querystring query_string = urlencode(kwargs, doseq=True) if kwargs else "" @@ -826,6 +845,9 @@ class Sanic(BaseSanic): await result async def _run_request_middleware(self, request, request_name=None): + print(self.request_middleware) + print(self.named_request_middleware) + print(request_name) # The if improves speed. I don't know why named_middleware = self.named_request_middleware.get( request_name, deque() diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 0c5393ca..ba1b1951 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -119,6 +119,8 @@ class Blueprint(BaseSanic): future.version or self.version, future.name, future.ignore_body, + future.websocket, + future.subprotocols, ) route = app._apply_route(apply_route) @@ -136,8 +138,9 @@ class Blueprint(BaseSanic): route_names = [route.name for route in routes if route] # Middleware - for future in self._future_middleware: - app._apply_middleware(future, route_names) + if route_names: + for future in self._future_middleware: + app._apply_middleware(future, route_names) # Exceptions for future in self._future_exceptions: diff --git a/sanic/mixins/listeners.py b/sanic/mixins/listeners.py index 6c27bc1d..1688f8b7 100644 --- a/sanic/mixins/listeners.py +++ b/sanic/mixins/listeners.py @@ -1,6 +1,6 @@ from enum import Enum, auto from functools import partial -from typing import Set +from typing import List from sanic.models.futures import FutureListener @@ -17,7 +17,7 @@ class ListenerEvent(str, Enum): class ListenerMixin: def __init__(self, *args, **kwargs) -> None: - self._future_listeners: Set[FutureListener] = set() + self._future_listeners: List[FutureListener] = list() def _apply_listener(self, listener: FutureListener): raise NotImplementedError @@ -32,7 +32,7 @@ class ListenerMixin: nonlocal apply future_listener = FutureListener(listener, event) - self._future_listeners.add(future_listener) + self._future_listeners.append(future_listener) if apply: self._apply_listener(future_listener) return listener diff --git a/sanic/mixins/middleware.py b/sanic/mixins/middleware.py index f05c02b5..03db8752 100644 --- a/sanic/mixins/middleware.py +++ b/sanic/mixins/middleware.py @@ -1,12 +1,12 @@ from functools import partial -from typing import Set +from typing import List from sanic.models.futures import FutureMiddleware class MiddlewareMixin: def __init__(self, *args, **kwargs) -> None: - self._future_middleware: Set[FutureMiddleware] = set() + self._future_middleware: List[FutureMiddleware] = list() def _apply_middleware(self, middleware: FutureMiddleware): raise NotImplementedError @@ -27,7 +27,7 @@ class MiddlewareMixin: nonlocal apply future_middleware = FutureMiddleware(middleware, attach_to) - self._future_middleware.add(future_middleware) + self._future_middleware.append(future_middleware) if apply: self._apply_middleware(future_middleware) return middleware diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index 0929091f..e8a09a53 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -52,11 +52,6 @@ class RouteMixin: of type :class:`FutureRoute` """ - # TODO: - # - run when applying future, not here - if websocket: - self.enable_websocket() - # Fix case where the user did not prefix the URL with a / # and will probably get confused as to why it's not working if not uri.startswith("/"): @@ -85,20 +80,6 @@ class RouteMixin: # variable will be a tuple of (existing routes, handler fn) _, handler = handler - # TODO: - # - move websocket handler out and attach it when applying - if websocket: - websocket_handler = partial( - self._websocket_handler, - handler, - subprotocols=subprotocols, - ) - websocket_handler.__name__ = ( - "websocket_handler_" + handler.__name__ - ) - websocket_handler.is_websocket = True - handler = websocket_handler - # TODO: # - THink this thru.... do we want all routes namespaced? # - @@ -119,6 +100,8 @@ class RouteMixin: version, name, ignore_body, + websocket, + subprotocols, ) self._future_routes.add(route) diff --git a/sanic/models/futures.py b/sanic/models/futures.py index bc68a9b3..dc48d0db 100644 --- a/sanic/models/futures.py +++ b/sanic/models/futures.py @@ -13,6 +13,8 @@ FutureRoute = namedtuple( "version", "name", "ignore_body", + "websocket", + "subprotocols", ], ) FutureListener = namedtuple("FutureListener", ["listener", "event"]) diff --git a/sanic/router.py b/sanic/router.py index 216fd455..e56727c5 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -1,7 +1,7 @@ from functools import lru_cache from typing import FrozenSet, Iterable, List, Optional, Union -from sanic_routing import BaseRouter +from sanic_routing import BaseRouter, route from sanic_routing.exceptions import NoMethod from sanic_routing.exceptions import NotFound as RoutingNotFound from sanic_routing.route import Route @@ -157,3 +157,26 @@ class Router(BaseRouter): ): handler = getattr(handler.view_class, request.method.lower()) return hasattr(handler, "is_stream") + + # @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 diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index e5b42059..0ec821bf 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -353,6 +353,29 @@ def test_bp_middleware(app): assert response.text == "FAIL" +def test_bp_middleware_with_route(app): + blueprint = Blueprint("test_bp_middleware") + + @blueprint.middleware("response") + async def process_response(request, response): + return text("OK") + + @app.route("/") + async def handler(request): + return text("FAIL") + + @blueprint.route("/bp") + async def bp_handler(request): + return text("FAIL") + + app.blueprint(blueprint) + + request, response = app.test_client.get("/bp") + + assert response.status == 200 + assert response.text == "OK" + + def test_bp_middleware_order(app): blueprint = Blueprint("test_bp_middleware_order") order = list() @@ -715,6 +738,9 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name): ) app.blueprint(bp) + print(app.router.name_index) + print(app.router.static_routes) + print(app.router.dynamic_routes) uri = app.url_for("static", name="static.testing") assert uri == "/static/test.file" @@ -825,6 +851,7 @@ def test_strict_slashes_behavior_adoption(app): assert app.test_client.get("/test")[1].status == 200 assert app.test_client.get("/test/")[1].status == 404 + app.router.finalized = False bp = Blueprint("bp") @bp.get("/one", strict_slashes=False)