diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md index b420a523..49b7c0b8 100644 --- a/docs/sanic/routing.md +++ b/docs/sanic/routing.md @@ -266,4 +266,38 @@ 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' ``` diff --git a/sanic/router.py b/sanic/router.py index 062fecc8..79faaf1e 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -67,6 +67,7 @@ class Router: def __init__(self): self.routes_all = {} + self.routes_names = {} self.routes_static = {} self.routes_dynamic = defaultdict(list) self.routes_always_check = [] @@ -125,13 +126,12 @@ class Router: # Add versions with and without trailing / slash_is_missing = ( - not uri[-1] == '/' - and not self.routes_all.get(uri + '/', False) + not uri[-1] == '/' and not self.routes_all.get(uri + '/', False) ) without_slash_is_missing = ( - uri[-1] == '/' - and not self.routes_all.get(uri[:-1], False) - and not uri == '/' + uri[-1] == '/' and not + self.routes_all.get(uri[:-1], False) and not + uri == '/' ) # add version with trailing slash if slash_is_missing: @@ -229,22 +229,26 @@ class Router: else: route = self.routes_all.get(uri) + # prefix the handler name with the blueprint name + # if available + if hasattr(handler, '__blueprintname__'): + handler_name = '{}.{}'.format( + handler.__blueprintname__, name or handler.__name__) + else: + handler_name = name or getattr(handler, '__name__', None) + if route: route = merge_route(route, methods, handler) else: - # prefix the handler name with the blueprint name - # if available - if hasattr(handler, '__blueprintname__'): - handler_name = '{}.{}'.format( - handler.__blueprintname__, name or handler.__name__) - else: - handler_name = name or getattr(handler, '__name__', None) - route = Route( handler=handler, methods=methods, pattern=pattern, parameters=parameters, name=handler_name, uri=uri) 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']: self.routes_always_check.append(route) elif parameters: @@ -265,6 +269,11 @@ class Router: uri = host + uri try: 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: raise RouteDoesNotExist("Route was not registered: {}".format(uri)) @@ -289,11 +298,7 @@ class Router: if not view_name: return (None, None) - for uri, route in self.routes_all.items(): - if route.name == view_name: - return uri, route - - return (None, None) + return self.routes_names.get(view_name, (None, None)) def get(self, request): """Get a request handler based on the URL of the request, or raises an diff --git a/tests/test_named_routes.py b/tests/test_named_routes.py index 730a2206..ca377e8d 100644 --- a/tests/test_named_routes.py +++ b/tests/test_named_routes.py @@ -7,7 +7,6 @@ import pytest from sanic import Sanic from sanic.blueprints import Blueprint from sanic.response import text -from sanic.router import RouteExists, RouteDoesNotExist from sanic.exceptions import URLBuildError from sanic.constants import HTTP_METHODS @@ -360,11 +359,7 @@ def test_overload_routes(): return text('OK1') @app.route('/overload', methods=['POST', 'PUT'], name='route_second') - async def handler2(request): - return text('OK2') - - @app.route('/overload2', methods=['POST', 'PUT'], name='route_third') - async def handler3(request): + async def handler1(request): return text('OK2') request, response = app.test_client.get(app.url_for('route_first')) @@ -376,16 +371,18 @@ def test_overload_routes(): 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') - with pytest.raises(URLBuildError): - app.url_for('handler2') - - with pytest.raises(URLBuildError): - app.url_for('route_second') - - assert app.url_for('route_third') == '/overload2' - with pytest.raises(URLBuildError): - app.url_for('handler3') + assert app.url_for('route_first') == '/overload' + assert app.url_for('route_second') == app.url_for('route_first')