diff --git a/CHANGES/445.bugfix b/CHANGES/445.bugfix new file mode 100644 index 000000000..d1f45d31c --- /dev/null +++ b/CHANGES/445.bugfix @@ -0,0 +1 @@ +Fix unintended quoting of braces (``[]``) in the keys of a query. diff --git a/docs/api.rst b/docs/api.rst index e5db8ffc2..60f35b76e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -439,6 +439,10 @@ section generates a new *URL* instance. >>> URL.build(scheme="http", host="example.com", query_string="a=b") URL('http://example.com/?a=b') + >>> URL.build(scheme="http", host="example.com", + ... query=[("a[]", "b"), ("a[]", "c")]) + URL('http://example.com/?a[]=b&a[]=c') + >>> URL.build() URL('') @@ -452,6 +456,11 @@ section generates a new *URL* instance. .. note:: Only one of ``query`` or ``query_string`` should be passed then AssertionError will be raised. + .. versionchanged:: 1.4.3 + + The ``'[]'`` characters are not escaped in the keys of the query + because some endpoints expect those to be present when specifying arrays. + .. method:: URL.with_scheme(scheme) Return a new URL with *scheme* replaced: @@ -564,6 +573,14 @@ section generates a new *URL* instance. URL('http://example.com/path?b=2') >>> URL('http://example.com/path?a=b&b=1').with_query([('b', '2')]) URL('http://example.com/path?b=2') + >>> URL('http://example.com/path?a[]=1&b[]=2')\ + ... .with_query([("a[]", "b"), ("a[]", "c")]) + URL('http://example.com/path?a[]=b&a[]=c') + + .. versionchanged:: 1.4.3 + + The ``'[]'`` characters are not escaped in the keys of the query + because some endpoints expect those to be present when specifying arrays. .. method:: URL.update_query(query) URL.update_query(**kwargs) @@ -610,11 +627,19 @@ section generates a new *URL* instance. URL('http://example.com/path?a=b&c=d') >>> URL('http://example.com/path?a=b').update_query('c=d&c=f') URL('http://example.com/path?a=b&c=d&c=f') + >>> URL('http://example.com/path?a=b')\ + ... .update_query([("c[]", "d"), ("c[]", "e")]) + URL('http://example.com/path?a=b&c[]=d&c[]=e') .. versionchanged:: 1.0 All multiple key/value pairs are applied to the multi-dictionary. + .. versionchanged:: 1.4.3 + + The ``'[]'`` characters are not escaped in the keys of the query + because some endpoints expect those to be present when specifying arrays. + .. method:: URL.with_fragment(fragment) Return a new URL with *fragment* replaced, auto-encode *fragment* if needed. diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 36c1de379..e5b1e6c91 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -219,3 +219,9 @@ def test_update_query_multiple_keys(): u2 = url.update_query([("a", "3"), ("a", "4")]) assert str(u2) == "http://example.com/path?a=3&a=4" + + +def test_with_query_no_escape_braces(): + url = URL("http://example.com/get") + url2 = url.with_query([("a[]", "3"), ("a[]", "4")]) + assert str(url2) == "http://example.com/get?a[]=3&a[]=4" diff --git a/yarl/__init__.py b/yarl/__init__.py index 3ec3fadbf..16a486b0b 100644 --- a/yarl/__init__.py +++ b/yarl/__init__.py @@ -126,7 +126,8 @@ class URL: _QUOTER = _Quoter() _PATH_QUOTER = _Quoter(safe="@:", protected="/+") _QUERY_QUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True) - _QUERY_PART_QUOTER = _Quoter(safe="?/:@", qs=True) + _QUERY_PART_KEY_QUOTER = _Quoter(safe="?/:@[]", qs=True) + _QUERY_PART_VALUE_QUOTER = _Quoter(safe="?/:@", qs=True) _FRAGMENT_QUOTER = _Quoter(safe="?/:@") _UNQUOTER = _Unquoter() @@ -881,9 +882,11 @@ def _get_str_query(self, *args, **kwargs): if query is None: query = "" elif isinstance(query, Mapping): - quoter = self._QUERY_PART_QUOTER + key_quoter = self._QUERY_PART_KEY_QUOTER + val_quoter = self._QUERY_PART_VALUE_QUOTER query = "&".join( - quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items() + key_quoter(k) + "=" + val_quoter(self._query_var(v)) + for k, v in query.items() ) elif isinstance(query, str): query = self._QUERY_QUOTER(query) @@ -892,9 +895,10 @@ def _get_str_query(self, *args, **kwargs): "Invalid query type: bytes, bytearray and " "memoryview are forbidden" ) elif isinstance(query, Sequence): - quoter = self._QUERY_PART_QUOTER + key_quoter = self._QUERY_PART_KEY_QUOTER + val_quoter = self._QUERY_PART_VALUE_QUOTER query = "&".join( - quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query + key_quoter(k) + "=" + val_quoter(self._query_var(v)) for k, v in query ) else: raise TypeError(