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( matched_params = re.findall(
self.router.parameter_pattern, uri) 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: for match in matched_params:
name, _type, pattern = self.router.parse_parameter_string( name, _type, pattern = self.router.parse_parameter_string(
match) match)
@ -271,11 +281,9 @@ class Sanic:
# parse the remainder of the keyword arguments into a querystring # parse the remainder of the keyword arguments into a querystring
if kwargs: if kwargs:
query_string = urlencode(kwargs) query_string = urlencode(kwargs, doseq=True)
out = urlunparse(( # scheme://netloc/path;parameters?query#fragment
'', '', out, out = urlunparse((scheme, netloc, out, '', query_string, anchor))
'', query_string, ''
))
return out return out

View File

@ -6,7 +6,11 @@ PORT = 42101
async def local_request(method, uri, cookies=None, *args, **kwargs): async def local_request(method, uri, cookies=None, *args, **kwargs):
if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')):
url = uri
else:
url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri) url = 'http://{host}:{port}{uri}'.format(host=HOST, port=PORT, uri=uri)
log.info(url) log.info(url)
async with aiohttp.ClientSession(cookies=cookies) as session: async with aiohttp.ClientSession(cookies=cookies) as session:
async with getattr( async with getattr(

View File

@ -5,7 +5,7 @@ from sanic import Sanic
from sanic.response import text from sanic.response import text
from sanic.views import HTTPMethodView from sanic.views import HTTPMethodView
from sanic.blueprints import Blueprint 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 from sanic.exceptions import URLBuildError
import string import string
@ -39,6 +39,30 @@ def test_simple_url_for_getting(simple_app):
assert response.text == letter 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(): def test_fails_if_endpoint_not_found():
app = Sanic('fail_url_build') 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) 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 = ( COMPLEX_PARAM_URL = (
'/<foo:int>/<four_letter_string:[A-z]{4}>/' '/<foo:int>/<four_letter_string:[A-z]{4}>/'
'<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>') '<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>')