diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 7e9953e0..a5942d6a 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -1,6 +1,8 @@ from collections import defaultdict, namedtuple +from types import MethodType from sanic.constants import HTTP_METHODS +from sanic.exceptions import SanicTypeException from sanic.views import CompositionView FutureRoute = namedtuple('Route', @@ -48,7 +50,7 @@ class Blueprint: methods=future.methods, host=future.host or self.host, strict_slashes=future.strict_slashes - )(future.handler) + )(future.handler) for future in self.websocket_routes: # attach the blueprint name to the handler so that it can be @@ -60,7 +62,7 @@ class Blueprint: uri=uri, host=future.host or self.host, strict_slashes=future.strict_slashes - )(future.handler) + )(future.handler) # Middleware for future in self.middlewares: @@ -93,10 +95,14 @@ class Blueprint: :param uri: endpoint at which the route will be accessible. :param methods: list of acceptable HTTP methods. """ + def decorator(handler): + if isinstance(handler, MethodType): + raise SanicTypeException("You can`t add a instance method as a blueprint router hander") route = FutureRoute(handler, uri, methods, host, strict_slashes) self.routes.append(route) return handler + return decorator def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None, @@ -120,6 +126,8 @@ class Blueprint: # handle composition view differently if isinstance(handler, CompositionView): methods = handler.handlers.keys() + if isinstance(handler, MethodType): + raise SanicTypeException("You can`t add a instance method as a blueprint router hander") self.route(uri=uri, methods=methods, host=host, strict_slashes=strict_slashes)(handler) @@ -130,10 +138,12 @@ class Blueprint: :param uri: endpoint at which the route will be accessible. """ + def decorator(handler): route = FutureRoute(handler, uri, [], host, strict_slashes) self.websocket_routes.append(route) return handler + return decorator def add_websocket_route(self, handler, uri, host=None): @@ -152,13 +162,16 @@ class Blueprint: :param event: Event to listen to. """ + def decorator(listener): self.listeners[event].append(listener) return listener + return decorator def middleware(self, *args, **kwargs): """Create a blueprint middleware from a decorated function.""" + def register_middleware(_middleware): future_middleware = FutureMiddleware(_middleware, args, kwargs) self.middlewares.append(future_middleware) @@ -174,10 +187,12 @@ class Blueprint: def exception(self, *args, **kwargs): """Create a blueprint exception from a decorated function.""" + def decorator(handler): exception = FutureException(handler, args, kwargs) self.exceptions.append(exception) return handler + return decorator def static(self, uri, file_or_directory, *args, **kwargs): diff --git a/sanic/exceptions.py b/sanic/exceptions.py index e30c178c..0af34390 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -97,7 +97,6 @@ INTERNAL_SERVER_ERROR_HTML = ''' class SanicException(Exception): - def __init__(self, message, status_code=None): super().__init__(message) @@ -105,6 +104,10 @@ class SanicException(Exception): self.status_code = status_code +class SanicTypeException(Exception): + pass + + class NotFound(SanicException): status_code = 404 diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 46726836..d745cf81 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -1,10 +1,12 @@ import asyncio import inspect -from sanic import Sanic +import pytest + +from sanic import Sanic, response from sanic.blueprints import Blueprint -from sanic.response import json, text -from sanic.exceptions import NotFound, ServerError, InvalidUsage +from sanic.exceptions import NotFound, ServerError, InvalidUsage, SanicTypeException +from sanic.response import text # ------------------------------------------------------------ # @@ -134,7 +136,6 @@ def test_several_bp_with_host(): def handler2(request): return text('Hello3') - app.blueprint(bp) app.blueprint(bp2) @@ -201,7 +202,6 @@ def test_bp_exception_handler(): request, response = app.test_client.get('/1') assert response.status == 400 - request, response = app.test_client.get('/2') assert response.status == 200 assert response.text == 'OK' @@ -349,3 +349,25 @@ def test_bp_shorthand(): 'Sec-WebSocket-Version': '13'}) assert response.status == 101 assert ev.is_set() + + +def test_blueprint_handler_type_error(): + class Manage(object): + def __init__(self): + self.blueprint = Blueprint(str(type(self).__name__)) + self.blueprint.add_route(self.info, '/info') + + async def info(self, request): + return response.json({'id': id(self)}) + + def register(self, app: Sanic): + app.blueprint(self.blueprint) + + def myfunc(): + app = Sanic() + manager = Manage() + manager.register(app) + + with pytest.raises(SanicTypeException) as execinfo: + myfunc() + execinfo.match(r'.*?instance method.*?')