rebase
This commit is contained in:
parent
48aa51b739
commit
d614823013
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user