Issue #13866: add *quote_via* argument to urlencode.

Patch by samwyse, completed by Arnon Yaari, and reviewed by
Martin Panter.
This commit is contained in:
R David Murray 2015-05-17 20:44:50 -04:00
parent 1dbee9460e
commit c17686f071
5 changed files with 47 additions and 18 deletions

View file

@ -519,7 +519,8 @@ task isn't already covered by the URL parsing functions above.
Example: ``unquote_to_bytes('a%26%EF')`` yields ``b'a&\xef'``. Example: ``unquote_to_bytes('a%26%EF')`` yields ``b'a&\xef'``.
.. function:: urlencode(query, doseq=False, safe='', encoding=None, errors=None) .. function:: urlencode(query, doseq=False, safe='', encoding=None, \
errors=None, quote_via=quote_plus)
Convert a mapping object or a sequence of two-element tuples, which may Convert a mapping object or a sequence of two-element tuples, which may
contain :class:`str` or :class:`bytes` objects, to a "percent-encoded" contain :class:`str` or :class:`bytes` objects, to a "percent-encoded"
@ -528,8 +529,16 @@ task isn't already covered by the URL parsing functions above.
properly encoded to bytes, otherwise it would result in a :exc:`TypeError`. properly encoded to bytes, otherwise it would result in a :exc:`TypeError`.
The resulting string is a series of ``key=value`` pairs separated by ``'&'`` The resulting string is a series of ``key=value`` pairs separated by ``'&'``
characters, where both *key* and *value* are quoted using :func:`quote_plus` characters, where both *key* and *value* are quoted using the *quote_via*
above. When a sequence of two-element tuples is used as the *query* function. By default, :func:`quote_plus` is used to quote the values, which
means spaces are quoted as a ``'+'`` character and '/' characters are
encoded as ``%2F``, which follows the standard for GET requests
(``application/x-www-form-urlencoded``). An alternate function that can be
passed as *quote_via* is :func:`quote`, which will encode spaces as ``%20``
and not encode '/' characters. For maximum control of what is quoted, use
``quote`` and specify a value for *safe*.
When a sequence of two-element tuples is used as the *query*
argument, the first element of each tuple is a key and the second is a argument, the first element of each tuple is a key and the second is a
value. The value element in itself can be a sequence and in that case, if value. The value element in itself can be a sequence and in that case, if
the optional parameter *doseq* is evaluates to *True*, individual the optional parameter *doseq* is evaluates to *True*, individual
@ -538,7 +547,7 @@ task isn't already covered by the URL parsing functions above.
string will match the order of parameter tuples in the sequence. string will match the order of parameter tuples in the sequence.
The *safe*, *encoding*, and *errors* parameters are passed down to The *safe*, *encoding*, and *errors* parameters are passed down to
:func:`quote_plus` (the *encoding* and *errors* parameters are only passed *quote_via* (the *encoding* and *errors* parameters are only passed
when a query element is a :class:`str`). when a query element is a :class:`str`).
To reverse this encoding process, :func:`parse_qs` and :func:`parse_qsl` are To reverse this encoding process, :func:`parse_qs` and :func:`parse_qsl` are
@ -550,6 +559,9 @@ task isn't already covered by the URL parsing functions above.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
Query parameter supports bytes and string objects. Query parameter supports bytes and string objects.
.. versionadded:: 3.5
*quote_via* parameter.
.. seealso:: .. seealso::

View file

@ -622,6 +622,10 @@ urllib
sent. (Contributed by Matej Cepl in :issue:`19494` and Akshit Khurana in sent. (Contributed by Matej Cepl in :issue:`19494` and Akshit Khurana in
:issue:`7159`.) :issue:`7159`.)
* A new :func:`~urllib.parse.urlencode` parameter *quote_via* provides a way to
control the encoding of query parts if needed. (Contributed by Samwyse and
Arnon Yaari in :issue:`13866`.)
wsgiref wsgiref
------- -------

View file

@ -785,6 +785,16 @@ class UrlParseTestCase(unittest.TestCase):
result = urllib.parse.urlencode({'a': Trivial()}, True) result = urllib.parse.urlencode({'a': Trivial()}, True)
self.assertEqual(result, 'a=trivial') self.assertEqual(result, 'a=trivial')
def test_urlencode_quote_via(self):
result = urllib.parse.urlencode({'a': 'some value'})
self.assertEqual(result, "a=some+value")
result = urllib.parse.urlencode({'a': 'some value/another'},
quote_via=urllib.parse.quote)
self.assertEqual(result, "a=some%20value%2Fanother")
result = urllib.parse.urlencode({'a': 'some value/another'},
safe='/', quote_via=urllib.parse.quote)
self.assertEqual(result, "a=some%20value/another")
def test_quote_from_bytes(self): def test_quote_from_bytes(self):
self.assertRaises(TypeError, urllib.parse.quote_from_bytes, 'foo') self.assertRaises(TypeError, urllib.parse.quote_from_bytes, 'foo')
result = urllib.parse.quote_from_bytes(b'archaeological arcana') result = urllib.parse.quote_from_bytes(b'archaeological arcana')

View file

@ -750,7 +750,8 @@ def quote_from_bytes(bs, safe='/'):
_safe_quoters[safe] = quoter = Quoter(safe).__getitem__ _safe_quoters[safe] = quoter = Quoter(safe).__getitem__
return ''.join([quoter(char) for char in bs]) return ''.join([quoter(char) for char in bs])
def urlencode(query, doseq=False, safe='', encoding=None, errors=None): def urlencode(query, doseq=False, safe='', encoding=None, errors=None,
quote_via=quote_plus):
"""Encode a dict or sequence of two-element tuples into a URL query string. """Encode a dict or sequence of two-element tuples into a URL query string.
If any values in the query arg are sequences and doseq is true, each If any values in the query arg are sequences and doseq is true, each
@ -762,8 +763,8 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
The components of a query arg may each be either a string or a bytes type. The components of a query arg may each be either a string or a bytes type.
The safe, encoding, and errors parameters are passed down to quote_plus() The safe, encoding, and errors parameters are passed down to the function
(encoding and errors only if a component is a str). specified by quote_via (encoding and errors only if a component is a str).
""" """
if hasattr(query, "items"): if hasattr(query, "items"):
@ -789,27 +790,27 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
if not doseq: if not doseq:
for k, v in query: for k, v in query:
if isinstance(k, bytes): if isinstance(k, bytes):
k = quote_plus(k, safe) k = quote_via(k, safe)
else: else:
k = quote_plus(str(k), safe, encoding, errors) k = quote_via(str(k), safe, encoding, errors)
if isinstance(v, bytes): if isinstance(v, bytes):
v = quote_plus(v, safe) v = quote_via(v, safe)
else: else:
v = quote_plus(str(v), safe, encoding, errors) v = quote_via(str(v), safe, encoding, errors)
l.append(k + '=' + v) l.append(k + '=' + v)
else: else:
for k, v in query: for k, v in query:
if isinstance(k, bytes): if isinstance(k, bytes):
k = quote_plus(k, safe) k = quote_via(k, safe)
else: else:
k = quote_plus(str(k), safe, encoding, errors) k = quote_via(str(k), safe, encoding, errors)
if isinstance(v, bytes): if isinstance(v, bytes):
v = quote_plus(v, safe) v = quote_via(v, safe)
l.append(k + '=' + v) l.append(k + '=' + v)
elif isinstance(v, str): elif isinstance(v, str):
v = quote_plus(v, safe, encoding, errors) v = quote_via(v, safe, encoding, errors)
l.append(k + '=' + v) l.append(k + '=' + v)
else: else:
try: try:
@ -817,15 +818,15 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
x = len(v) x = len(v)
except TypeError: except TypeError:
# not a sequence # not a sequence
v = quote_plus(str(v), safe, encoding, errors) v = quote_via(str(v), safe, encoding, errors)
l.append(k + '=' + v) l.append(k + '=' + v)
else: else:
# loop over the sequence # loop over the sequence
for elt in v: for elt in v:
if isinstance(elt, bytes): if isinstance(elt, bytes):
elt = quote_plus(elt, safe) elt = quote_via(elt, safe)
else: else:
elt = quote_plus(str(elt), safe, encoding, errors) elt = quote_via(str(elt), safe, encoding, errors)
l.append(k + '=' + elt) l.append(k + '=' + elt)
return '&'.join(l) return '&'.join(l)

View file

@ -47,6 +47,8 @@ Core and Builtins
Library Library
------- -------
- Issue #13866: *quote_via* argument added to urllib.parse.urlencode.
- Issue #20098: New mangle_from_ policy option for email, default True - Issue #20098: New mangle_from_ policy option for email, default True
for compat32, but False for all other policies. for compat32, but False for all other policies.