Merge pull request #901 from lixxu/master

add name option for route building
This commit is contained in:
Raphael Deem 2017-08-31 15:29:03 -07:00 committed by GitHub
commit 158da0927a
5 changed files with 569 additions and 86 deletions

View File

@ -239,3 +239,65 @@ def handler(request):
app.blueprint(bp) app.blueprint(bp)
``` ```
## User defined route name
You can pass `name` to change the route name to avoid using the default name (`handler.__name__`).
```python
app = Sanic('test_named_route')
@app.get('/get', name='get_handler')
def handler(request):
return text('OK')
# then you need use `app.url_for('get_handler')`
# instead of # `app.url_for('handler')`
# It also works for blueprints
bp = Blueprint('test_named_bp')
@bp.get('/bp/get', name='get_handler')
def handler(request):
return text('OK')
app.blueprint(bp)
# then you need use `app.url_for('test_named_bp.get_handler')`
# instead of `app.url_for('test_named_bp.handler')`
# different names can be used for same url with different methods
@app.get('/test', name='route_test')
def handler(request):
return text('OK')
@app.post('/test', name='route_post')
def handler2(request):
return text('OK POST')
@app.put('/test', name='route_put')
def handler3(request):
return text('OK PUT')
# below url are the same, you can use any of them
# '/test'
app.url_for('route_test')
# app.url_for('route_post')
# app.url_for('route_put')
# for same handler name with different methods
# you need specify the name (it's url_for issue)
@app.get('/get')
def handler(request):
return text('OK')
@app.post('/post', name='post_handler')
def handler(request):
return text('OK')
# then
# app.url_for('handler') == '/get'
# app.url_for('post_handler') == '/post'
```

View File

@ -112,7 +112,7 @@ class Sanic:
# Decorator # Decorator
def route(self, uri, methods=frozenset({'GET'}), host=None, def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=None, stream=False, version=None): strict_slashes=None, stream=False, version=None, name=None):
"""Decorate a function to be registered as a route """Decorate a function to be registered as a route
:param uri: path of the URL :param uri: path of the URL
@ -120,6 +120,8 @@ class Sanic:
:param host: :param host:
:param strict_slashes: :param strict_slashes:
:param stream: :param stream:
:param version:
:param name: user defined route name for url_for
:return: decorated function :return: decorated function
""" """
@ -139,48 +141,56 @@ class Sanic:
handler.is_stream = stream handler.is_stream = stream
self.router.add(uri=uri, methods=methods, handler=handler, self.router.add(uri=uri, methods=methods, handler=handler,
host=host, strict_slashes=strict_slashes, host=host, strict_slashes=strict_slashes,
version=version) version=version, name=name)
return handler return handler
return response return response
# Shorthand method decorators # Shorthand method decorators
def get(self, uri, host=None, strict_slashes=None, version=None): def get(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=frozenset({"GET"}), host=host, return self.route(uri, methods=frozenset({"GET"}), host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def post(self, uri, host=None, strict_slashes=None, stream=False, def post(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=frozenset({"POST"}), host=host, return self.route(uri, methods=frozenset({"POST"}), host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def put(self, uri, host=None, strict_slashes=None, stream=False, def put(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=frozenset({"PUT"}), host=host, return self.route(uri, methods=frozenset({"PUT"}), host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def head(self, uri, host=None, strict_slashes=None, version=None): def head(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=frozenset({"HEAD"}), host=host, return self.route(uri, methods=frozenset({"HEAD"}), host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def options(self, uri, host=None, strict_slashes=None, version=None): def options(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host, return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def patch(self, uri, host=None, strict_slashes=None, stream=False, def patch(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=frozenset({"PATCH"}), host=host, return self.route(uri, methods=frozenset({"PATCH"}), host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def delete(self, uri, host=None, strict_slashes=None, version=None): def delete(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=frozenset({"DELETE"}), host=host, return self.route(uri, methods=frozenset({"DELETE"}), host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None, def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=None, version=None): strict_slashes=None, version=None, name=None):
"""A helper method to register class instance or """A helper method to register class instance or
functions as a handler to the application url functions as a handler to the application url
routes. routes.
@ -190,6 +200,9 @@ class Sanic:
:param methods: list or tuple of methods allowed, these are overridden :param methods: list or tuple of methods allowed, these are overridden
if using a HTTPMethodView if using a HTTPMethodView
:param host: :param host:
:param strict_slashes:
:param version:
:param name: user defined route name for url_for
:return: function or class instance :return: function or class instance
""" """
stream = False stream = False
@ -217,12 +230,12 @@ class Sanic:
self.route(uri=uri, methods=methods, host=host, self.route(uri=uri, methods=methods, host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version)(handler) version=version, name=name)(handler)
return handler return handler
# Decorator # Decorator
def websocket(self, uri, host=None, strict_slashes=None, def websocket(self, uri, host=None, strict_slashes=None,
subprotocols=None): subprotocols=None, name=None):
"""Decorate a function to be registered as a websocket route """Decorate a function to be registered as a websocket route
:param uri: path of the URL :param uri: path of the URL
:param subprotocols: optional list of strings with the supported :param subprotocols: optional list of strings with the supported
@ -265,19 +278,19 @@ class Sanic:
self.router.add(uri=uri, handler=websocket_handler, self.router.add(uri=uri, handler=websocket_handler,
methods=frozenset({'GET'}), host=host, methods=frozenset({'GET'}), host=host,
strict_slashes=strict_slashes) strict_slashes=strict_slashes, name=name)
return handler return handler
return response return response
def add_websocket_route(self, handler, uri, host=None, def add_websocket_route(self, handler, uri, host=None,
strict_slashes=None): strict_slashes=None, name=None):
"""A helper method to register a function as a websocket route.""" """A helper method to register a function as a websocket route."""
if strict_slashes is None: if strict_slashes is None:
strict_slashes = self.strict_slashes strict_slashes = self.strict_slashes
return self.websocket(uri, host=host, return self.websocket(uri, host=host, strict_slashes=strict_slashes,
strict_slashes=strict_slashes)(handler) name=name)(handler)
def enable_websocket(self, enable=True): def enable_websocket(self, enable=True):
"""Enable or disable the support for websocket. """Enable or disable the support for websocket.
@ -400,8 +413,7 @@ class Sanic:
uri, route = self.router.find_route_by_view_name(view_name) uri, route = self.router.find_route_by_view_name(view_name)
if not uri or not route: if not uri or not route:
raise URLBuildError( raise URLBuildError('Endpoint with name `{}` was not found'.format(
'Endpoint with name `{}` was not found'.format(
view_name)) view_name))
if uri != '/' and uri.endswith('/'): if uri != '/' and uri.endswith('/'):

View File

@ -5,7 +5,7 @@ from sanic.views import CompositionView
FutureRoute = namedtuple('Route', FutureRoute = namedtuple('Route',
['handler', 'uri', 'methods', 'host', ['handler', 'uri', 'methods', 'host',
'strict_slashes', 'stream', 'version']) 'strict_slashes', 'stream', 'version', 'name'])
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host']) FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs']) FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs']) FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
@ -53,13 +53,13 @@ class Blueprint:
version = future.version or self.version version = future.version or self.version
app.route( app.route(uri=uri[1:] if uri.startswith('//') else uri,
uri=uri[1:] if uri.startswith('//') else uri,
methods=future.methods, methods=future.methods,
host=future.host or self.host, host=future.host or self.host,
strict_slashes=future.strict_slashes, strict_slashes=future.strict_slashes,
stream=future.stream, stream=future.stream,
version=version version=version,
name=future.name,
)(future.handler) )(future.handler)
for future in self.websocket_routes: for future in self.websocket_routes:
@ -68,10 +68,10 @@ class Blueprint:
future.handler.__blueprintname__ = self.name future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available # Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri uri = url_prefix + future.uri if url_prefix else future.uri
app.websocket( app.websocket(uri=uri,
uri=uri,
host=future.host or self.host, host=future.host or self.host,
strict_slashes=future.strict_slashes strict_slashes=future.strict_slashes,
name=future.name,
)(future.handler) )(future.handler)
# Middleware # Middleware
@ -100,7 +100,7 @@ class Blueprint:
app.listener(event)(listener) app.listener(event)(listener)
def route(self, uri, methods=frozenset({'GET'}), host=None, def route(self, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=None, stream=False, version=None): strict_slashes=None, stream=False, version=None, name=None):
"""Create a blueprint route from a decorated function. """Create a blueprint route from a decorated function.
:param uri: endpoint at which the route will be accessible. :param uri: endpoint at which the route will be accessible.
@ -111,19 +111,24 @@ class Blueprint:
def decorator(handler): def decorator(handler):
route = FutureRoute( route = FutureRoute(
handler, uri, methods, host, strict_slashes, stream, version) handler, uri, methods, host, strict_slashes, stream, version,
name)
self.routes.append(route) self.routes.append(route)
return handler return handler
return decorator return decorator
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None, def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
strict_slashes=None, version=None): strict_slashes=None, version=None, name=None):
"""Create a blueprint route from a function. """Create a blueprint route from a function.
:param handler: function for handling uri requests. Accepts function, :param handler: function for handling uri requests. Accepts function,
or class instance with a view_class method. or class instance with a view_class method.
:param uri: endpoint at which the route will be accessible. :param uri: endpoint at which the route will be accessible.
:param methods: list of acceptable HTTP methods. :param methods: list of acceptable HTTP methods.
:param host:
:param strict_slashes:
:param version:
:param name: user defined route name for url_for
:return: function or class instance :return: function or class instance
""" """
# Handle HTTPMethodView differently # Handle HTTPMethodView differently
@ -142,10 +147,12 @@ class Blueprint:
methods = handler.handlers.keys() methods = handler.handlers.keys()
self.route(uri=uri, methods=methods, host=host, self.route(uri=uri, methods=methods, host=host,
strict_slashes=strict_slashes, version=version)(handler) strict_slashes=strict_slashes, version=version,
name=name)(handler)
return handler return handler
def websocket(self, uri, host=None, strict_slashes=None, version=None): def websocket(self, uri, host=None, strict_slashes=None, version=None,
name=None):
"""Create a blueprint websocket route from a decorated function. """Create a blueprint websocket route from a decorated function.
:param uri: endpoint at which the route will be accessible. :param uri: endpoint at which the route will be accessible.
@ -155,12 +162,13 @@ class Blueprint:
def decorator(handler): def decorator(handler):
route = FutureRoute(handler, uri, [], host, strict_slashes, route = FutureRoute(handler, uri, [], host, strict_slashes,
False, version) False, version, name)
self.websocket_routes.append(route) self.websocket_routes.append(route)
return handler return handler
return decorator return decorator
def add_websocket_route(self, handler, uri, host=None, version=None): def add_websocket_route(self, handler, uri, host=None, version=None,
name=None):
"""Create a blueprint websocket route from a function. """Create a blueprint websocket route from a function.
:param handler: function for handling uri requests. Accepts function, :param handler: function for handling uri requests. Accepts function,
@ -168,7 +176,7 @@ class Blueprint:
:param uri: endpoint at which the route will be accessible. :param uri: endpoint at which the route will be accessible.
:return: function or class instance :return: function or class instance
""" """
self.websocket(uri=uri, host=host, version=version)(handler) self.websocket(uri=uri, host=host, version=version, name=name)(handler)
return handler return handler
def listener(self, event): def listener(self, event):
@ -214,36 +222,44 @@ class Blueprint:
self.statics.append(static) self.statics.append(static)
# Shorthand method decorators # Shorthand method decorators
def get(self, uri, host=None, strict_slashes=None, version=None): def get(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=["GET"], host=host, return self.route(uri, methods=["GET"], host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def post(self, uri, host=None, strict_slashes=None, stream=False, def post(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=["POST"], host=host, return self.route(uri, methods=["POST"], host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def put(self, uri, host=None, strict_slashes=None, stream=False, def put(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=["PUT"], host=host, return self.route(uri, methods=["PUT"], host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def head(self, uri, host=None, strict_slashes=None, version=None): def head(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=["HEAD"], host=host, return self.route(uri, methods=["HEAD"], host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def options(self, uri, host=None, strict_slashes=None, version=None): def options(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=["OPTIONS"], host=host, return self.route(uri, methods=["OPTIONS"], host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)
def patch(self, uri, host=None, strict_slashes=None, stream=False, def patch(self, uri, host=None, strict_slashes=None, stream=False,
version=None): version=None, name=None):
return self.route(uri, methods=["PATCH"], host=host, return self.route(uri, methods=["PATCH"], host=host,
strict_slashes=strict_slashes, stream=stream, strict_slashes=strict_slashes, stream=stream,
version=version) version=version, name=name)
def delete(self, uri, host=None, strict_slashes=None, version=None): def delete(self, uri, host=None, strict_slashes=None, version=None,
name=None):
return self.route(uri, methods=["DELETE"], host=host, return self.route(uri, methods=["DELETE"], host=host,
strict_slashes=strict_slashes, version=version) strict_slashes=strict_slashes, version=version,
name=name)

View File

@ -67,6 +67,7 @@ class Router:
def __init__(self): def __init__(self):
self.routes_all = {} self.routes_all = {}
self.routes_names = {}
self.routes_static = {} self.routes_static = {}
self.routes_dynamic = defaultdict(list) self.routes_dynamic = defaultdict(list)
self.routes_always_check = [] self.routes_always_check = []
@ -99,7 +100,7 @@ class Router:
return name, _type, pattern return name, _type, pattern
def add(self, uri, methods, handler, host=None, strict_slashes=False, def add(self, uri, methods, handler, host=None, strict_slashes=False,
version=None): version=None, name=None):
"""Add a handler to the route list """Add a handler to the route list
:param uri: path to match :param uri: path to match
@ -118,29 +119,28 @@ class Router:
else: else:
uri = "/".join(["/v{}".format(str(version)), uri]) uri = "/".join(["/v{}".format(str(version)), uri])
# add regular version # add regular version
self._add(uri, methods, handler, host) self._add(uri, methods, handler, host, name)
if strict_slashes: if strict_slashes:
return return
# Add versions with and without trailing / # Add versions with and without trailing /
slash_is_missing = ( slash_is_missing = (
not uri[-1] == '/' not uri[-1] == '/' and not self.routes_all.get(uri + '/', False)
and not self.routes_all.get(uri + '/', False)
) )
without_slash_is_missing = ( without_slash_is_missing = (
uri[-1] == '/' uri[-1] == '/' and not
and not self.routes_all.get(uri[:-1], False) self.routes_all.get(uri[:-1], False) and not
and not uri == '/' uri == '/'
) )
# add version with trailing slash # add version with trailing slash
if slash_is_missing: if slash_is_missing:
self._add(uri + '/', methods, handler, host) self._add(uri + '/', methods, handler, host, name)
# add version without trailing slash # add version without trailing slash
elif without_slash_is_missing: elif without_slash_is_missing:
self._add(uri[:-1], methods, handler, host) self._add(uri[:-1], methods, handler, host, name)
def _add(self, uri, methods, handler, host=None): def _add(self, uri, methods, handler, host=None, name=None):
"""Add a handler to the route list """Add a handler to the route list
:param uri: path to match :param uri: path to match
@ -161,7 +161,7 @@ class Router:
"host strings, not {!r}".format(host)) "host strings, not {!r}".format(host))
for host_ in host: for host_ in host:
self.add(uri, methods, handler, host_) self.add(uri, methods, handler, host_, name)
return return
# Dict for faster lookups of if method allowed # Dict for faster lookups of if method allowed
@ -229,22 +229,26 @@ class Router:
else: else:
route = self.routes_all.get(uri) route = self.routes_all.get(uri)
if route:
route = merge_route(route, methods, handler)
else:
# prefix the handler name with the blueprint name # prefix the handler name with the blueprint name
# if available # if available
if hasattr(handler, '__blueprintname__'): if hasattr(handler, '__blueprintname__'):
handler_name = '{}.{}'.format( handler_name = '{}.{}'.format(
handler.__blueprintname__, handler.__name__) handler.__blueprintname__, name or handler.__name__)
else: else:
handler_name = getattr(handler, '__name__', None) handler_name = name or getattr(handler, '__name__', None)
if route:
route = merge_route(route, methods, handler)
else:
route = Route( route = Route(
handler=handler, methods=methods, pattern=pattern, handler=handler, methods=methods, pattern=pattern,
parameters=parameters, name=handler_name, uri=uri) parameters=parameters, name=handler_name, uri=uri)
self.routes_all[uri] = route self.routes_all[uri] = route
pairs = self.routes_names.get(handler_name)
if not (pairs and (pairs[0] + '/' == uri or uri + '/' == pairs[0])):
self.routes_names[handler_name] = (uri, route)
if properties['unhashable']: if properties['unhashable']:
self.routes_always_check.append(route) self.routes_always_check.append(route)
elif parameters: elif parameters:
@ -265,6 +269,11 @@ class Router:
uri = host + uri uri = host + uri
try: try:
route = self.routes_all.pop(uri) route = self.routes_all.pop(uri)
for handler_name, pairs in self.routes_names.items():
if pairs[0] == uri:
self.routes_names.pop(handler_name)
break
except KeyError: except KeyError:
raise RouteDoesNotExist("Route was not registered: {}".format(uri)) raise RouteDoesNotExist("Route was not registered: {}".format(uri))
@ -289,11 +298,7 @@ class Router:
if not view_name: if not view_name:
return (None, None) return (None, None)
for uri, route in self.routes_all.items(): return self.routes_names.get(view_name, (None, None))
if route.name == view_name:
return uri, route
return (None, None)
def get(self, request): def get(self, request):
"""Get a request handler based on the URL of the request, or raises an """Get a request handler based on the URL of the request, or raises an

388
tests/test_named_routes.py Normal file
View File

@ -0,0 +1,388 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import pytest
from sanic import Sanic
from sanic.blueprints import Blueprint
from sanic.response import text
from sanic.exceptions import URLBuildError
from sanic.constants import HTTP_METHODS
# ------------------------------------------------------------ #
# UTF-8
# ------------------------------------------------------------ #
@pytest.mark.parametrize('method', HTTP_METHODS)
def test_versioned_named_routes_get(method):
app = Sanic('test_shorhand_routes_get')
bp = Blueprint('test_bp', url_prefix='/bp')
method = method.lower()
route_name = 'route_{}'.format(method)
route_name2 = 'route2_{}'.format(method)
func = getattr(app, method)
if callable(func):
@func('/{}'.format(method), version=1, name=route_name)
def handler(request):
return text('OK')
else:
print(func)
raise
func = getattr(bp, method)
if callable(func):
@func('/{}'.format(method), version=1, name=route_name2)
def handler2(request):
return text('OK')
else:
print(func)
raise
app.blueprint(bp)
assert app.router.routes_all['/v1/{}'.format(method)].name == route_name
route = app.router.routes_all['/v1/bp/{}'.format(method)]
assert route.name == 'test_bp.{}'.format(route_name2)
assert app.url_for(route_name) == '/v1/{}'.format(method)
url = app.url_for('test_bp.{}'.format(route_name2))
assert url == '/v1/bp/{}'.format(method)
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_default_routes_get():
app = Sanic('test_shorhand_routes_get')
@app.get('/get')
def handler(request):
return text('OK')
assert app.router.routes_all['/get'].name == 'handler'
assert app.url_for('handler') == '/get'
def test_shorthand_named_routes_get():
app = Sanic('test_shorhand_routes_get')
bp = Blueprint('test_bp', url_prefix='/bp')
@app.get('/get', name='route_get')
def handler(request):
return text('OK')
@bp.get('/get', name='route_bp')
def handler2(request):
return text('Blueprint')
app.blueprint(bp)
assert app.router.routes_all['/get'].name == 'route_get'
assert app.url_for('route_get') == '/get'
with pytest.raises(URLBuildError):
app.url_for('handler')
assert app.router.routes_all['/bp/get'].name == 'test_bp.route_bp'
assert app.url_for('test_bp.route_bp') == '/bp/get'
with pytest.raises(URLBuildError):
app.url_for('test_bp.handler2')
def test_shorthand_named_routes_post():
app = Sanic('test_shorhand_routes_post')
@app.post('/post', name='route_name')
def handler(request):
return text('OK')
assert app.router.routes_all['/post'].name == 'route_name'
assert app.url_for('route_name') == '/post'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_named_routes_put():
app = Sanic('test_shorhand_routes_put')
@app.put('/put', name='route_put')
def handler(request):
assert request.stream is None
return text('OK')
assert app.is_request_stream is False
assert app.router.routes_all['/put'].name == 'route_put'
assert app.url_for('route_put') == '/put'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_named_routes_delete():
app = Sanic('test_shorhand_routes_delete')
@app.delete('/delete', name='route_delete')
def handler(request):
assert request.stream is None
return text('OK')
assert app.is_request_stream is False
assert app.router.routes_all['/delete'].name == 'route_delete'
assert app.url_for('route_delete') == '/delete'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_named_routes_patch():
app = Sanic('test_shorhand_routes_patch')
@app.patch('/patch', name='route_patch')
def handler(request):
assert request.stream is None
return text('OK')
assert app.is_request_stream is False
assert app.router.routes_all['/patch'].name == 'route_patch'
assert app.url_for('route_patch') == '/patch'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_named_routes_head():
app = Sanic('test_shorhand_routes_head')
@app.head('/head', name='route_head')
def handler(request):
assert request.stream is None
return text('OK')
assert app.is_request_stream is False
assert app.router.routes_all['/head'].name == 'route_head'
assert app.url_for('route_head') == '/head'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_shorthand_named_routes_options():
app = Sanic('test_shorhand_routes_options')
@app.options('/options', name='route_options')
def handler(request):
assert request.stream is None
return text('OK')
assert app.is_request_stream is False
assert app.router.routes_all['/options'].name == 'route_options'
assert app.url_for('route_options') == '/options'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_named_static_routes():
app = Sanic('test_dynamic_route')
@app.route('/test', name='route_test')
async def handler1(request):
return text('OK1')
@app.route('/pizazz', name='route_pizazz')
async def handler2(request):
return text('OK2')
assert app.router.routes_all['/test'].name == 'route_test'
assert app.router.routes_static['/test'].name == 'route_test'
assert app.url_for('route_test') == '/test'
with pytest.raises(URLBuildError):
app.url_for('handler1')
assert app.router.routes_all['/pizazz'].name == 'route_pizazz'
assert app.router.routes_static['/pizazz'].name == 'route_pizazz'
assert app.url_for('route_pizazz') == '/pizazz'
with pytest.raises(URLBuildError):
app.url_for('handler2')
def test_named_dynamic_route():
app = Sanic('test_dynamic_route')
results = []
@app.route('/folder/<name>', name='route_dynamic')
async def handler(request, name):
results.append(name)
return text('OK')
assert app.router.routes_all['/folder/<name>'].name == 'route_dynamic'
assert app.url_for('route_dynamic', name='test') == '/folder/test'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_dynamic_named_route_regex():
app = Sanic('test_dynamic_route_regex')
@app.route('/folder/<folder_id:[A-Za-z0-9]{0,4}>', name='route_re')
async def handler(request, folder_id):
return text('OK')
route = app.router.routes_all['/folder/<folder_id:[A-Za-z0-9]{0,4}>']
assert route.name == 'route_re'
assert app.url_for('route_re', folder_id='test') == '/folder/test'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_dynamic_named_route_path():
app = Sanic('test_dynamic_route_path')
@app.route('/<path:path>/info', name='route_dynamic_path')
async def handler(request, path):
return text('OK')
route = app.router.routes_all['/<path:path>/info']
assert route.name == 'route_dynamic_path'
assert app.url_for('route_dynamic_path', path='path/1') == '/path/1/info'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_dynamic_named_route_unhashable():
app = Sanic('test_dynamic_route_unhashable')
@app.route('/folder/<unhashable:[A-Za-z0-9/]+>/end/',
name='route_unhashable')
async def handler(request, unhashable):
return text('OK')
route = app.router.routes_all['/folder/<unhashable:[A-Za-z0-9/]+>/end/']
assert route.name == 'route_unhashable'
url = app.url_for('route_unhashable', unhashable='test/asdf')
assert url == '/folder/test/asdf/end'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_websocket_named_route():
app = Sanic('test_websocket_route')
ev = asyncio.Event()
@app.websocket('/ws', name='route_ws')
async def handler(request, ws):
assert ws.subprotocol is None
ev.set()
assert app.router.routes_all['/ws'].name == 'route_ws'
assert app.url_for('route_ws') == '/ws'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_websocket_named_route_with_subprotocols():
app = Sanic('test_websocket_route')
results = []
@app.websocket('/ws', subprotocols=['foo', 'bar'], name='route_ws')
async def handler(request, ws):
results.append(ws.subprotocol)
assert app.router.routes_all['/ws'].name == 'route_ws'
assert app.url_for('route_ws') == '/ws'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_static_add_named_route():
app = Sanic('test_static_add_route')
async def handler1(request):
return text('OK1')
async def handler2(request):
return text('OK2')
app.add_route(handler1, '/test', name='route_test')
app.add_route(handler2, '/test2', name='route_test2')
assert app.router.routes_all['/test'].name == 'route_test'
assert app.router.routes_static['/test'].name == 'route_test'
assert app.url_for('route_test') == '/test'
with pytest.raises(URLBuildError):
app.url_for('handler1')
assert app.router.routes_all['/test2'].name == 'route_test2'
assert app.router.routes_static['/test2'].name == 'route_test2'
assert app.url_for('route_test2') == '/test2'
with pytest.raises(URLBuildError):
app.url_for('handler2')
def test_dynamic_add_named_route():
app = Sanic('test_dynamic_add_route')
results = []
async def handler(request, name):
results.append(name)
return text('OK')
app.add_route(handler, '/folder/<name>', name='route_dynamic')
assert app.router.routes_all['/folder/<name>'].name == 'route_dynamic'
assert app.url_for('route_dynamic', name='test') == '/folder/test'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_dynamic_add_named_route_unhashable():
app = Sanic('test_dynamic_add_route_unhashable')
async def handler(request, unhashable):
return text('OK')
app.add_route(handler, '/folder/<unhashable:[A-Za-z0-9/]+>/end/',
name='route_unhashable')
route = app.router.routes_all['/folder/<unhashable:[A-Za-z0-9/]+>/end/']
assert route.name == 'route_unhashable'
url = app.url_for('route_unhashable', unhashable='folder1')
assert url == '/folder/folder1/end'
with pytest.raises(URLBuildError):
app.url_for('handler')
def test_overload_routes():
app = Sanic('test_dynamic_route')
@app.route('/overload', methods=['GET'], name='route_first')
async def handler1(request):
return text('OK1')
@app.route('/overload', methods=['POST', 'PUT'], name='route_second')
async def handler1(request):
return text('OK2')
request, response = app.test_client.get(app.url_for('route_first'))
assert response.text == 'OK1'
request, response = app.test_client.post(app.url_for('route_first'))
assert response.text == 'OK2'
request, response = app.test_client.put(app.url_for('route_first'))
assert response.text == 'OK2'
request, response = app.test_client.get(app.url_for('route_second'))
assert response.text == 'OK1'
request, response = app.test_client.post(app.url_for('route_second'))
assert response.text == 'OK2'
request, response = app.test_client.put(app.url_for('route_second'))
assert response.text == 'OK2'
assert app.router.routes_all['/overload'].name == 'route_first'
with pytest.raises(URLBuildError):
app.url_for('handler1')
assert app.url_for('route_first') == '/overload'
assert app.url_for('route_second') == app.url_for('route_first')