improve url_for to support multi values for one arg, add _anchor/_external/_scheme options

This commit is contained in:
lixxu 2017-02-09 16:44:23 +08:00
parent 7401facc21
commit cf2a363e5e
3 changed files with 58 additions and 9 deletions

View File

@ -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

View File

@ -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(

View File

@ -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 = (
'/<foo:int>/<four_letter_string:[A-z]{4}>/'
'<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>')
@ -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/<param>') # noqa
@second_print.route('/foo/<param>') # noqa
def foo_with_param(request, param):
return text(
'foo from second : {}'.format(param))