rebase
This commit is contained in:
		| @@ -4,8 +4,7 @@ from functools import lru_cache | ||||
| from .exceptions import NotFound, InvalidUsage | ||||
| from .views import CompositionView | ||||
|  | ||||
| Route = namedtuple( | ||||
|     'Route', | ||||
| Route = namedtuple('Route', | ||||
|     ['handler', 'methods', 'pattern', 'parameters', 'name']) | ||||
| Parameter = namedtuple('Parameter', ['name', 'cast']) | ||||
|  | ||||
| @@ -70,6 +69,28 @@ class Router: | ||||
|         self.routes_always_check = [] | ||||
|         self.hosts = None | ||||
|  | ||||
|     def __str__(self): | ||||
|         """ | ||||
|         The typical user inspecting the router will likely want to see | ||||
|         the routes available. Provide a simple representation. | ||||
|         """ | ||||
|         def _route_to_str(uri, route): | ||||
|             out = 'name={0.name}, methods={0.methods}, URI={1}>\n'.format( | ||||
|                 route, uri) | ||||
|  | ||||
|             if route.handler.__doc__: | ||||
|                 out += '{}\n'.format(route.handler.__doc__) | ||||
|  | ||||
|             out += '\n' | ||||
|  | ||||
|             return out | ||||
|  | ||||
|         out = '' | ||||
|         for uri, route in self.routes_all.items(): | ||||
|             out += _route_to_str(uri, route) | ||||
|  | ||||
|         return out | ||||
|  | ||||
|     def parse_parameter_string(self, parameter_string): | ||||
|         """ | ||||
|         Parse a parameter string into its constituent name, type, and pattern | ||||
| @@ -130,11 +151,16 @@ class Router: | ||||
|         properties = {"unhashable": None} | ||||
|  | ||||
|         def add_parameter(match): | ||||
|             # We could receive NAME or NAME:PATTERN | ||||
|             name = match.group(1) | ||||
|             name, _type, pattern = self.parse_parameter_string(name) | ||||
|             pattern = 'string' | ||||
|             if ':' in name: | ||||
|                 name, pattern = name.split(':', 1) | ||||
|  | ||||
|             parameter = Parameter( | ||||
|                 name=name, cast=_type) | ||||
|             default = (str, pattern) | ||||
|             # Pull from pre-configured types | ||||
|             _type, pattern = REGEX_TYPES.get(pattern, default) | ||||
|             parameter = Parameter(name=name, cast=_type) | ||||
|             parameters.append(parameter) | ||||
|  | ||||
|             # Mark the whole route as unhashable if it has the hash key in it | ||||
| @@ -146,7 +172,7 @@ class Router: | ||||
|  | ||||
|             return '({})'.format(pattern) | ||||
|  | ||||
|         pattern_string = re.sub(self.parameter_pattern, add_parameter, uri) | ||||
|         pattern_string = re.sub(r'<(.+?)>', add_parameter, uri) | ||||
|         pattern = re.compile(r'^{}$'.format(pattern_string)) | ||||
|  | ||||
|         def merge_route(route, methods, handler): | ||||
|   | ||||
| @@ -17,6 +17,7 @@ from .response import HTTPResponse | ||||
| from .router import Router | ||||
| from .server import serve, serve_multiple, HttpProtocol | ||||
| from .static import register as static_register | ||||
| from .views import CompositionView | ||||
|  | ||||
|  | ||||
| class Sanic: | ||||
| @@ -120,7 +121,16 @@ class Sanic: | ||||
|         """ | ||||
|         # Handle HTTPMethodView differently | ||||
|         if hasattr(handler, 'view_class'): | ||||
|             methods = frozenset(HTTP_METHODS) | ||||
|             methods = set() | ||||
|  | ||||
|             for method in HTTP_METHODS: | ||||
|                 if getattr(handler.view_class, method.lower(), None): | ||||
|                     methods.add(method) | ||||
|  | ||||
|         # handle composition view differently | ||||
|         if isinstance(handler, CompositionView): | ||||
|             methods = handler.handlers.keys() | ||||
|  | ||||
|         self.route(uri=uri, methods=methods, host=host)(handler) | ||||
|         return handler | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| from .exceptions import InvalidUsage | ||||
| from .constants import HTTP_METHODS | ||||
|  | ||||
|  | ||||
| class HTTPMethodView: | ||||
| @@ -40,11 +41,7 @@ class HTTPMethodView: | ||||
|  | ||||
|     def dispatch_request(self, request, *args, **kwargs): | ||||
|         handler = getattr(self, request.method.lower(), None) | ||||
|         if handler: | ||||
|             return handler(request, *args, **kwargs) | ||||
|         raise InvalidUsage( | ||||
|             'Method {} not allowed for URL {}'.format( | ||||
|                 request.method, request.url), status_code=405) | ||||
|         return handler(request, *args, **kwargs) | ||||
|  | ||||
|     @classmethod | ||||
|     def as_view(cls, *class_args, **class_kwargs): | ||||
| @@ -89,15 +86,15 @@ class CompositionView: | ||||
|  | ||||
|     def add(self, methods, handler): | ||||
|         for method in methods: | ||||
|             if method not in HTTP_METHODS: | ||||
|                 raise InvalidUsage( | ||||
|                     '{} is not a valid HTTP method.'.format(method)) | ||||
|  | ||||
|             if method in self.handlers: | ||||
|                 raise KeyError( | ||||
|                     'Method {} already is registered.'.format(method)) | ||||
|                 raise InvalidUsage( | ||||
|                     'Method {} is already registered.'.format(method)) | ||||
|             self.handlers[method] = handler | ||||
|  | ||||
|     def __call__(self, request, *args, **kwargs): | ||||
|         handler = self.handlers.get(request.method.upper(), None) | ||||
|         if handler is None: | ||||
|             raise InvalidUsage( | ||||
|                 'Method {} not allowed for URL {}'.format( | ||||
|                     request.method, request.url), status_code=405) | ||||
|         handler = self.handlers[request.method.upper()] | ||||
|         return handler(request, *args, **kwargs) | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import pytest as pytest | ||||
|  | ||||
| from sanic import Sanic | ||||
| from sanic.exceptions import InvalidUsage | ||||
| from sanic.response import text, HTTPResponse | ||||
| from sanic.views import HTTPMethodView | ||||
| from sanic.views import HTTPMethodView, CompositionView | ||||
| from sanic.blueprints import Blueprint | ||||
| from sanic.request import Request | ||||
| from sanic.utils import sanic_endpoint_test | ||||
| @@ -196,3 +197,56 @@ def test_with_decorator(): | ||||
|     request, response = sanic_endpoint_test(app, method="get") | ||||
|     assert response.text == 'I am get method' | ||||
|     assert results[0] == 1 | ||||
|  | ||||
|  | ||||
| def test_composition_view_rejects_incorrect_methods(): | ||||
|     def foo(request): | ||||
|         return text('Foo') | ||||
|  | ||||
|     view = CompositionView() | ||||
|  | ||||
|     with pytest.raises(InvalidUsage) as e: | ||||
|         view.add(['GET', 'FOO'], foo) | ||||
|  | ||||
|     assert str(e.value) == 'FOO is not a valid HTTP method.' | ||||
|  | ||||
|  | ||||
| def test_composition_view_rejects_duplicate_methods(): | ||||
|     def foo(request): | ||||
|         return text('Foo') | ||||
|  | ||||
|     view = CompositionView() | ||||
|  | ||||
|     with pytest.raises(InvalidUsage) as e: | ||||
|         view.add(['GET', 'POST', 'GET'], foo) | ||||
|  | ||||
|     assert str(e.value) == 'Method GET is already registered.' | ||||
|  | ||||
|  | ||||
| def test_composition_view_runs_methods_as_expected(): | ||||
|     app = Sanic('test_composition_view') | ||||
|  | ||||
|     view = CompositionView() | ||||
|     view.add(['GET', 'POST', 'PUT'], lambda x: text('first method')) | ||||
|     view.add(['DELETE', 'PATCH'], lambda x: text('second method')) | ||||
|  | ||||
|     app.add_route(view, '/') | ||||
|  | ||||
|     for method in ['GET', 'POST', 'PUT']: | ||||
|         request, response = sanic_endpoint_test(app, uri='/', method=method) | ||||
|         assert response.text == 'first method' | ||||
|  | ||||
|     for method in ['DELETE', 'PATCH']: | ||||
|         request, response = sanic_endpoint_test(app, uri='/', method=method) | ||||
|         assert response.text == 'second method' | ||||
|  | ||||
| def test_composition_view_rejects_invalid_methods(): | ||||
|     app = Sanic('test_composition_view') | ||||
|  | ||||
|     view = CompositionView() | ||||
|     view.add(['GET', 'POST', 'PUT'], lambda x: text('first method')) | ||||
|  | ||||
|     app.add_route(view, '/') | ||||
|     for method in ['DELETE', 'PATCH']: | ||||
|         request, response = sanic_endpoint_test(app, uri='/', method=method) | ||||
|         assert response.status == 405 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Suby Raman
					Suby Raman