diff --git a/sanic/sanic.py b/sanic/sanic.py index 951632be..7b3e5d22 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -230,6 +230,16 @@ class Sanic: matched_params = re.findall( self.router.parameter_pattern, uri) + # _method is only a placeholder now, don't know how to support it + kwargs.pop('_method', None) + anchor = kwargs.pop('_anchor', '') + # _external need SERVER_NAME in config or pass _server arg + external = kwargs.pop('_external', False) + scheme = kwargs.pop('_scheme', '') + if scheme and not external: + raise ValueError('When specifying _scheme, _external must be True') + + netloc = kwargs.pop('_server', self.config.get('SERVER_NAME', '')) for match in matched_params: name, _type, pattern = self.router.parse_parameter_string( match) @@ -271,11 +281,9 @@ class Sanic: # parse the remainder of the keyword arguments into a querystring if kwargs: - query_string = urlencode(kwargs) - out = urlunparse(( - '', '', out, - '', query_string, '' - )) + query_string = urlencode(kwargs, doseq=True) + # scheme://netloc/path;parameters?query#fragment + out = urlunparse((scheme, netloc, out, '', query_string, anchor)) return out diff --git a/sanic/utils.py b/sanic/utils.py index 8e8f8124..da13f478 100644 --- a/sanic/utils.py +++ b/sanic/utils.py @@ -6,7 +6,11 @@ PORT = 42101 async def local_request(method, uri, cookies=None, *args, **kwargs): - url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) + if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')): + url = uri + else: + url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) + log.info(url) async with aiohttp.ClientSession(cookies=cookies) as session: async with getattr( diff --git a/tests/test_url_building.py b/tests/test_url_building.py index 480313b3..f2377c3a 100644 --- a/tests/test_url_building.py +++ b/tests/test_url_building.py @@ -5,7 +5,7 @@ from sanic import Sanic from sanic.response import text from sanic.views import HTTPMethodView from sanic.blueprints import Blueprint -from sanic.utils import sanic_endpoint_test +from sanic.utils import sanic_endpoint_test, PORT as test_port from sanic.exceptions import URLBuildError import string @@ -39,6 +39,30 @@ def test_simple_url_for_getting(simple_app): assert response.text == letter +def test_simple_url_for_getting_with_duplicate_params(simple_app): + kw = dict(arg1=['value1', 'value2'], _anchor='anchor') + for letter in string.ascii_letters: + url = simple_app.url_for(letter, **kw) + + assert url == '/{}?arg1=value1&arg1=value2#anchor'.format(letter) + request, response = sanic_endpoint_test(simple_app, uri=url) + assert response.status == 200 + assert response.text == letter + + +def test_simple_url_for_getting_with_special_params(simple_app): + kw = dict(arg1='value1', _anchor='anchor', _scheme='http', + _server='localhost:{}'.format(test_port), _external=True) + url_fmt = 'http://localhost:{}/{}?arg1=value1#anchor' + for letter in string.ascii_letters: + url = simple_app.url_for(letter, **kw) + + assert url == url_fmt.format(test_port, letter) + request, response = sanic_endpoint_test(simple_app, uri=url) + assert response.status == 200 + assert response.text == letter + + def test_fails_if_endpoint_not_found(): app = Sanic('fail_url_build') @@ -75,6 +99,19 @@ def test_fails_url_build_if_param_not_passed(): assert 'Required parameter `Z` was not passed to url_for' in str(e.value) +def test_fails_url_build_if_params_not_passed(): + app = Sanic('fail_url_build') + + @app.route('/fail') + def fail(): + return text('this should fail') + + with pytest.raises(ValueError) as e: + app.url_for('fail', _scheme='http') + + assert str(e.value) == 'When specifying _scheme, _external must be True' + + COMPLEX_PARAM_URL = ( '///' '//') @@ -179,11 +216,11 @@ def blueprint_app(): return text( 'foo from first : {}'.format(param)) - @second_print.route('/foo') # noqa + @second_print.route('/foo') # noqa def foo(): return text('foo from second') - @second_print.route('/foo/') # noqa + @second_print.route('/foo/') # noqa def foo_with_param(request, param): return text( 'foo from second : {}'.format(param))