From a9b4fd4af8fbba8d62cd0d0f51c5f79eda3630ce Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 10:09:35 +0200 Subject: [PATCH 01/17] Support quoting lists when used in mappings Fixes aio-libs/aiohttp/issues/4714 --- tests/test_update_query.py | 37 +++++++++++++++++++++++++++++++++++++ yarl/__init__.py | 17 ++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 36c1de379..7a3192659 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -117,6 +117,43 @@ def test_with_query_list_int(): assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1" +def test_with_query_sequence(): + url = URL("http://example.com") + assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") + + +def test_with_query_sequence_tuple(): + url = URL("http://example.com") + assert url.with_query({"a": (1, 2)}) == URL("http://example.com/?a=1&a=2") + + +def test_with_query_sequence_mixed_types(): + url = URL("http://example.com") + assert url.with_query({"a": ["1", 2]}) == URL("http://example.com/?a=1&a=2") + + +def test_with_query_sequence_single_then_list(): + url = URL("http://example.com") + assert url.with_query({"a": 1, "b": [2, 3]}) == URL("http://example.com/?a=1&b=2&b=3") + + +def test_with_query_sequence_list_then_single(): + url = URL("http://example.com") + assert url.with_query({"a": [1, 2], "b": 3}) == URL("http://example.com/?a=1&a=2&b=3") + + +def test_with_query_sequence_nested_sequence(): + url = URL("http://example.com") + with pytest.raises(TypeError): + url.with_query({"a": [[1]]}) + + +def test_with_query_sequence_using_pairs(): + url = URL("http://example.com") + with pytest.raises(TypeError): + url.with_query([("a", [1, 2])]) + + def test_with_query_non_str(): url = URL("http://example.com") with pytest.raises(TypeError): diff --git a/yarl/__init__.py b/yarl/__init__.py index 3ec3fadbf..2af172608 100644 --- a/yarl/__init__.py +++ b/yarl/__init__.py @@ -852,6 +852,15 @@ def with_path(self, path, *, encoded=False): path = "/" + path return URL(self._val._replace(path=path, query="", fragment=""), encoded=True) + @classmethod + def _query_seq_pairs(cls, quoter, pairs): + for key, val in pairs: + if isinstance(val, (list, tuple)): + for v in val: + yield quoter(key) + "=" + quoter(cls._query_var(v)) + else: + yield quoter(key) + "=" + quoter(cls._query_var(val)) + @staticmethod def _query_var(v): if isinstance(v, str): @@ -882,9 +891,7 @@ def _get_str_query(self, *args, **kwargs): query = "" elif isinstance(query, Mapping): quoter = self._QUERY_PART_QUOTER - query = "&".join( - quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items() - ) + query = "&".join(self._query_seq_pairs(quoter, query.items())) elif isinstance(query, str): query = self._QUERY_QUOTER(query) elif isinstance(query, (bytes, bytearray, memoryview)): @@ -893,6 +900,10 @@ def _get_str_query(self, *args, **kwargs): ) elif isinstance(query, Sequence): quoter = self._QUERY_PART_QUOTER + # We don't expect sequence values if we're given a list of pairs + # already; only mappings like builtin `dict` which can't have the + # same key pointing to multiple values are allowed to use + # `_query_seq_pairs`. query = "&".join( quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query ) From 87b1544cd475ceb3e6f88b489877a8d1861135de Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 10:17:46 +0200 Subject: [PATCH 02/17] Add file to CHANGES/ --- CHANGES/443.feature | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CHANGES/443.feature diff --git a/CHANGES/443.feature b/CHANGES/443.feature new file mode 100644 index 000000000..143638d75 --- /dev/null +++ b/CHANGES/443.feature @@ -0,0 +1,6 @@ +These changes fix https://github.com/aio-libs/aiohttp/issues/4714, that is, +we now support the use of lists and tuples when quoting mappings such as +Python's builtin dict. As an example from the tests: + + url = URL("http://example.com") + assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") From 4688a312a43a66126472c190734e1588bb42815b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 10:42:57 +0200 Subject: [PATCH 03/17] Update documentation with new use of sequences --- docs/api.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index e5db8ffc2..959c15a4d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -547,6 +547,9 @@ section generates a new *URL* instance. The library accepts :class:`str` and :class:`int` as query argument values. + If a mapping such as :class:`dict` is used, the values may also be + :class:`list` or :class:`tuple` to represent a key has many values. + Please see :ref:`yarl-bools-support` for the reason why :class:`bool` is not supported out-of-the-box. @@ -556,6 +559,8 @@ section generates a new *URL* instance. URL('http://example.com/path?c=d') >>> URL('http://example.com/path?a=b').with_query({'c': 'd'}) URL('http://example.com/path?c=d') + >>> URL('http://example.com/path?a=b').with_query({'c': [1, 2]}) + URL('http://example.com/path?c=1&c=2') >>> URL('http://example.com/path?a=b').with_query({'кл': 'зн'}) URL('http://example.com/path?%D0%BA%D0%BB=%D0%B7%D0%BD') >>> URL('http://example.com/path?a=b').with_query(None) @@ -591,6 +596,9 @@ section generates a new *URL* instance. The library accepts :class:`str` and :class:`int` as query argument values. + If a mapping such as :class:`dict` is used, the values may also be + :class:`list` or :class:`tuple` to represent a key has many values. + Please see :ref:`yarl-bools-support` for the reason why :class:`bool` is not supported out-of-the-box. @@ -600,6 +608,8 @@ 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'}) URL('http://example.com/path?a=b&c=d') + >>> URL('http://example.com/path?a=b').update_query({'c': [1, 2]}) + URL('http://example.com/path?a=b&c=1&c=2') >>> URL('http://example.com/path?a=b').update_query({'кл': 'зн'}) URL('http://example.com/path?a=b&%D0%BA%D0%BB=%D0%B7%D0%BD') >>> URL('http://example.com/path?a=b&b=1').update_query(b='2') From 50ac939d207d9112a18d2657a667aaad8c10306a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 10:51:40 +0200 Subject: [PATCH 04/17] Apply flake8 suggestion on the new sequence tests --- tests/test_update_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 7a3192659..06afda980 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -134,12 +134,14 @@ def test_with_query_sequence_mixed_types(): def test_with_query_sequence_single_then_list(): url = URL("http://example.com") - assert url.with_query({"a": 1, "b": [2, 3]}) == URL("http://example.com/?a=1&b=2&b=3") + expected = URL("http://example.com/?a=1&b=2&b=3") + assert url.with_query({"a": 1, "b": [2, 3]}) == expected def test_with_query_sequence_list_then_single(): url = URL("http://example.com") - assert url.with_query({"a": [1, 2], "b": 3}) == URL("http://example.com/?a=1&a=2&b=3") + expected = URL("http://example.com/?a=1&a=2&b=3") + assert url.with_query({"a": [1, 2], "b": 3}) == expected def test_with_query_sequence_nested_sequence(): From d48f3437ba7f5e5322664842eb20b7eae00a4cdc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:18:11 +0200 Subject: [PATCH 05/17] Use pytest.mark.parametrize on query_sequence tests --- tests/test_update_query.py | 47 ++++++++++++-------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 06afda980..bea60887f 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -117,43 +117,26 @@ def test_with_query_list_int(): assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1" -def test_with_query_sequence(): +@pytest.mark.parametrize("query,expected", [ + ({"a": [1, 2]}, "http://example.com/?a=1&a=2"), + ({"a": (1, 2)}, "http://example.com/?a=1&a=2"), + ({"a": ["1", 2]}, "http://example.com/?a=1&a=2"), + ({"a": 1, "b": [2, 3]}, "http://example.com/?a=1&b=2&b=3"), + ({"a": [1, 2], "b": 3}, "http://example.com/?a=1&a=2&b=3"), +]) +def test_with_query_sequence(query, expected): url = URL("http://example.com") - assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") + assert url.with_query(query) == URL(expected) -def test_with_query_sequence_tuple(): - url = URL("http://example.com") - assert url.with_query({"a": (1, 2)}) == URL("http://example.com/?a=1&a=2") - - -def test_with_query_sequence_mixed_types(): - url = URL("http://example.com") - assert url.with_query({"a": ["1", 2]}) == URL("http://example.com/?a=1&a=2") - - -def test_with_query_sequence_single_then_list(): - url = URL("http://example.com") - expected = URL("http://example.com/?a=1&b=2&b=3") - assert url.with_query({"a": 1, "b": [2, 3]}) == expected - - -def test_with_query_sequence_list_then_single(): - url = URL("http://example.com") - expected = URL("http://example.com/?a=1&a=2&b=3") - assert url.with_query({"a": [1, 2], "b": 3}) == expected - - -def test_with_query_sequence_nested_sequence(): - url = URL("http://example.com") - with pytest.raises(TypeError): - url.with_query({"a": [[1]]}) - - -def test_with_query_sequence_using_pairs(): +@pytest.mark.parametrize("query", [ + {"a": [[1]]}, + [("a", [1, 2])], +]) +def test_with_query_sequence_invalid_use(query): url = URL("http://example.com") with pytest.raises(TypeError): - url.with_query([("a", [1, 2])]) + url.with_query(query) def test_with_query_non_str(): From 65d804bba5b68100edad12d2b4e0ad784fd8d7e1 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:25:22 +0200 Subject: [PATCH 06/17] Use pytest.param for cleaner test output --- tests/test_update_query.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index bea60887f..fbf382ff4 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -117,22 +117,36 @@ def test_with_query_list_int(): assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1" -@pytest.mark.parametrize("query,expected", [ - ({"a": [1, 2]}, "http://example.com/?a=1&a=2"), - ({"a": (1, 2)}, "http://example.com/?a=1&a=2"), - ({"a": ["1", 2]}, "http://example.com/?a=1&a=2"), - ({"a": 1, "b": [2, 3]}, "http://example.com/?a=1&b=2&b=3"), - ({"a": [1, 2], "b": 3}, "http://example.com/?a=1&a=2&b=3"), -]) +@pytest.mark.parametrize( + "query,expected", + [ + pytest.param({"a": [1, 2]}, "http://example.com/?a=1&a=2", id="list"), + pytest.param({"a": (1, 2)}, "http://example.com/?a=1&a=2", id="tuple"), + pytest.param({"a": ["1", 2]}, "http://example.com/?a=1&a=2", id="mixed types"), + pytest.param( + {"a": 1, "b": [2, 3]}, + "http://example.com/?a=1&b=2&b=3", + id="single then list", + ), + pytest.param( + {"a": [1, 2], "b": 3}, + "http://example.com/?a=1&a=2&b=3", + id="list then single", + ), + ], +) def test_with_query_sequence(query, expected): url = URL("http://example.com") assert url.with_query(query) == URL(expected) -@pytest.mark.parametrize("query", [ - {"a": [[1]]}, - [("a", [1, 2])], -]) +@pytest.mark.parametrize( + "query", + [ + pytest.param({"a": [[1]]}, id="nested"), + pytest.param([("a", [1, 2])], id="tuple list"), + ], +) def test_with_query_sequence_invalid_use(query): url = URL("http://example.com") with pytest.raises(TypeError): From 3baab57e71a19a7c559264be5dffc6c1a0fe8678 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:26:18 +0200 Subject: [PATCH 07/17] Use shorter lines in test_with_query_sequence params This helps keep the code more readable after fmt with black. --- tests/test_update_query.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index fbf382ff4..00e690037 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -120,24 +120,16 @@ def test_with_query_list_int(): @pytest.mark.parametrize( "query,expected", [ - pytest.param({"a": [1, 2]}, "http://example.com/?a=1&a=2", id="list"), - pytest.param({"a": (1, 2)}, "http://example.com/?a=1&a=2", id="tuple"), - pytest.param({"a": ["1", 2]}, "http://example.com/?a=1&a=2", id="mixed types"), - pytest.param( - {"a": 1, "b": [2, 3]}, - "http://example.com/?a=1&b=2&b=3", - id="single then list", - ), - pytest.param( - {"a": [1, 2], "b": 3}, - "http://example.com/?a=1&a=2&b=3", - id="list then single", - ), + pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), + pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), + pytest.param({"a": ["1", 2]}, "?a=1&a=2", id="mixed types"), + pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list",), + pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single",), ], ) def test_with_query_sequence(query, expected): url = URL("http://example.com") - assert url.with_query(query) == URL(expected) + assert url.with_query(query) == URL("http://example.com/" + expected) @pytest.mark.parametrize( From 55883ae89a1cbd6ffadee42e332d83369199db83 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:34:30 +0200 Subject: [PATCH 08/17] Reword changelog on use of sequences as dict values --- CHANGES/443.feature | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES/443.feature b/CHANGES/443.feature index 143638d75..11a10f7d3 100644 --- a/CHANGES/443.feature +++ b/CHANGES/443.feature @@ -1,6 +1,5 @@ -These changes fix https://github.com/aio-libs/aiohttp/issues/4714, that is, -we now support the use of lists and tuples when quoting mappings such as -Python's builtin dict. As an example from the tests: +Allow use of sequences such as :class:`list` and :class:`tuple` in the values +of a mapping such as :class:`dict` to represent that a key has many values: url = URL("http://example.com") assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") From eca89ccf5d97af17e3181700c89f7d0b2fca79a0 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:42:43 +0200 Subject: [PATCH 09/17] Add test cases for quoting sequences with ampersands --- tests/test_update_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 00e690037..357a04580 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -123,8 +123,10 @@ def test_with_query_list_int(): pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), pytest.param({"a": ["1", 2]}, "?a=1&a=2", id="mixed types"), - pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list",), - pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single",), + pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list"), + pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single"), + pytest.param({"a": ["1&a=2", 3]}, "?a=1%26a%3D2&a=3", id="ampersand then int"), + pytest.param({"a": [1, "2&a=3"]}, "?a=1&a=2%26a%3D3", id="int then ampersand"), ], ) def test_with_query_sequence(query, expected): From b2c74e13e92e91670209b083441e27694f4c2787 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:44:39 +0200 Subject: [PATCH 10/17] Use match= keyword for test_with_query_sequence_invalid_use --- tests/test_update_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 357a04580..4dbbc6161 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -143,7 +143,7 @@ def test_with_query_sequence(query, expected): ) def test_with_query_sequence_invalid_use(query): url = URL("http://example.com") - with pytest.raises(TypeError): + with pytest.raises(TypeError, match="Invalid variable type"): url.with_query(query) From 5a97e18572f5c5029549355a7b5220b1dd5b2419 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:51:39 +0200 Subject: [PATCH 11/17] Parametrize test_with_query_params with extra cases --- tests/test_update_query.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 4dbbc6161..6dd8f5b5a 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -226,16 +226,19 @@ def test_with_query_memoryview(): url.with_query(memoryview(b"123")) -def test_with_query_params(): - url = URL("http://example.com/get") - url2 = url.with_query([("key", "1;2;3")]) - assert str(url2) == "http://example.com/get?key=1%3B2%3B3" - - -def test_with_query_params2(): +@pytest.mark.parametrize( + "query,expected", + [ + pytest.param([("key", "1;2;3")], "?key=1%3B2%3B3", id="tuple list semicolon"), + pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"), + pytest.param([("key", "1&a=2")], "?key=1%26a%3D2", id="tuple list ampersand"), + pytest.param({"key": "1&a=2"}, "?key=1%26a%3D2", id="mapping ampersand"), + ], +) +def test_with_query_params(query, expected): url = URL("http://example.com/get") - url2 = url.with_query({"key": "1;2;3"}) - assert str(url2) == "http://example.com/get?key=1%3B2%3B3" + url2 = url.with_query(query) + assert str(url2) == ("http://example.com/get" + expected) def test_with_query_only(): From 6453bbe9d8a51ce198f534290bb6c8d519207e69 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:55:58 +0200 Subject: [PATCH 12/17] Add a test to ensure braces are not quoted --- tests/test_update_query.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 6dd8f5b5a..d28f71575 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -122,6 +122,7 @@ def test_with_query_list_int(): [ pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), + pytest.param({"a[]": [1, 2]}, "?a[]=1&a[]=2", id="key with braces"), pytest.param({"a": ["1", 2]}, "?a=1&a=2", id="mixed types"), pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list"), pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single"), From 95d9b3c235e62b82f5f5ffa624382d6244d98b95 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 15:59:20 +0200 Subject: [PATCH 13/17] Add tests to ensure keys are quoted properly --- tests/test_update_query.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index d28f71575..4cae3f1a3 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -123,7 +123,9 @@ def test_with_query_list_int(): pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), pytest.param({"a[]": [1, 2]}, "?a[]=1&a[]=2", id="key with braces"), + pytest.param({"&": [1, 2]}, "?%26=1&%26=2", id="quote key"), pytest.param({"a": ["1", 2]}, "?a=1&a=2", id="mixed types"), + pytest.param({"&": ["=", 2]}, "?%26=%3D&%26=2", id="quote key and value"), pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list"), pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single"), pytest.param({"a": ["1&a=2", 3]}, "?a=1%26a%3D2&a=3", id="ampersand then int"), @@ -234,6 +236,8 @@ def test_with_query_memoryview(): pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"), pytest.param([("key", "1&a=2")], "?key=1%26a%3D2", id="tuple list ampersand"), pytest.param({"key": "1&a=2"}, "?key=1%26a%3D2", id="mapping ampersand"), + pytest.param([("&", "=")], "?%26=%3D", id="tuple list quote key"), + pytest.param({"&": "="}, "?%26=%3D", id="mapping quote key"), ], ) def test_with_query_params(query, expected): From 773a0c7a9797e07a76b47b7ae32fa113eb699f36 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 16:00:23 +0200 Subject: [PATCH 14/17] Add tests for edge cases of quoting lists and tuples --- tests/test_update_query.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 4cae3f1a3..e7cafcef3 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -120,6 +120,10 @@ def test_with_query_list_int(): @pytest.mark.parametrize( "query,expected", [ + pytest.param({"a": []}, "?", id="empty list"), + pytest.param({"a": ()}, "?", id="empty tuple"), + pytest.param({"a": [1]}, "?a=1", id="single list"), + pytest.param({"a": (1,)}, "?a=1", id="single tuple"), pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), pytest.param({"a[]": [1, 2]}, "?a[]=1&a[]=2", id="key with braces"), From 5fc17fae82e578a06ce24e193b548825f59ff21d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 3 May 2020 09:49:21 +0200 Subject: [PATCH 15/17] Use tuples in pytest.parametrize --- tests/test_update_query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index e7cafcef3..5b8944ffe 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -118,7 +118,7 @@ def test_with_query_list_int(): @pytest.mark.parametrize( - "query,expected", + ("query", "expected"), [ pytest.param({"a": []}, "?", id="empty list"), pytest.param({"a": ()}, "?", id="empty tuple"), @@ -234,7 +234,7 @@ def test_with_query_memoryview(): @pytest.mark.parametrize( - "query,expected", + ("query" , "expected"), [ pytest.param([("key", "1;2;3")], "?key=1%3B2%3B3", id="tuple list semicolon"), pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"), From 167ff0f105e5aeb1549ef2594140b7c054319799 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 3 May 2020 10:01:00 +0200 Subject: [PATCH 16/17] Update test_with_query_sequence to compare str --- tests/test_update_query.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 5b8944ffe..8b16887e7 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -120,25 +120,26 @@ def test_with_query_list_int(): @pytest.mark.parametrize( ("query", "expected"), [ - pytest.param({"a": []}, "?", id="empty list"), - pytest.param({"a": ()}, "?", id="empty tuple"), - pytest.param({"a": [1]}, "?a=1", id="single list"), - pytest.param({"a": (1,)}, "?a=1", id="single tuple"), - pytest.param({"a": [1, 2]}, "?a=1&a=2", id="list"), - pytest.param({"a": (1, 2)}, "?a=1&a=2", id="tuple"), - pytest.param({"a[]": [1, 2]}, "?a[]=1&a[]=2", id="key with braces"), - pytest.param({"&": [1, 2]}, "?%26=1&%26=2", id="quote key"), - pytest.param({"a": ["1", 2]}, "?a=1&a=2", id="mixed types"), - pytest.param({"&": ["=", 2]}, "?%26=%3D&%26=2", id="quote key and value"), - pytest.param({"a": 1, "b": [2, 3]}, "?a=1&b=2&b=3", id="single then list"), - pytest.param({"a": [1, 2], "b": 3}, "?a=1&a=2&b=3", id="list then single"), - pytest.param({"a": ["1&a=2", 3]}, "?a=1%26a%3D2&a=3", id="ampersand then int"), - pytest.param({"a": [1, "2&a=3"]}, "?a=1&a=2%26a%3D3", id="int then ampersand"), + pytest.param({"a": []}, "", id="empty list"), + pytest.param({"a": ()}, "", id="empty tuple"), + pytest.param({"a": [1]}, "/?a=1", id="single list"), + pytest.param({"a": (1,)}, "/?a=1", id="single tuple"), + pytest.param({"a": [1, 2]}, "/?a=1&a=2", id="list"), + pytest.param({"a": (1, 2)}, "/?a=1&a=2", id="tuple"), + pytest.param({"a[]": [1, 2]}, "/?a[]=1&a[]=2", id="key with braces"), + pytest.param({"&": [1, 2]}, "/?%26=1&%26=2", id="quote key"), + pytest.param({"a": ["1", 2]}, "/?a=1&a=2", id="mixed types"), + pytest.param({"&": ["=", 2]}, "/?%26=%3D&%26=2", id="quote key and value"), + pytest.param({"a": 1, "b": [2, 3]}, "/?a=1&b=2&b=3", id="single then list"), + pytest.param({"a": [1, 2], "b": 3}, "/?a=1&a=2&b=3", id="list then single"), + pytest.param({"a": ["1&a=2", 3]}, "/?a=1%26a%3D2&a=3", id="ampersand then int"), + pytest.param({"a": [1, "2&a=3"]}, "/?a=1&a=2%26a%3D3", id="int then ampersand"), ], ) def test_with_query_sequence(query, expected): url = URL("http://example.com") - assert url.with_query(query) == URL("http://example.com/" + expected) + expected = "http://example.com{expected}".format_map(locals()) + assert str(url.with_query(query)) == expected @pytest.mark.parametrize( @@ -234,7 +235,7 @@ def test_with_query_memoryview(): @pytest.mark.parametrize( - ("query" , "expected"), + ("query", "expected"), [ pytest.param([("key", "1;2;3")], "?key=1%3B2%3B3", id="tuple list semicolon"), pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"), From 434c3cb144f1481317c3c23500a06c2575af3191 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 11 May 2020 12:17:32 +0200 Subject: [PATCH 17/17] Update tests to check query key[] is encoded --- tests/test_update_query.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 8b16887e7..f8224954a 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -126,7 +126,7 @@ def test_with_query_list_int(): pytest.param({"a": (1,)}, "/?a=1", id="single tuple"), pytest.param({"a": [1, 2]}, "/?a=1&a=2", id="list"), pytest.param({"a": (1, 2)}, "/?a=1&a=2", id="tuple"), - pytest.param({"a[]": [1, 2]}, "/?a[]=1&a[]=2", id="key with braces"), + pytest.param({"a[]": [1, 2]}, "/?a%5B%5D=1&a%5B%5D=2", id="key with braces"), pytest.param({"&": [1, 2]}, "/?%26=1&%26=2", id="quote key"), pytest.param({"a": ["1", 2]}, "/?a=1&a=2", id="mixed types"), pytest.param({"&": ["=", 2]}, "/?%26=%3D&%26=2", id="quote key and value"), @@ -243,6 +243,12 @@ def test_with_query_memoryview(): pytest.param({"key": "1&a=2"}, "?key=1%26a%3D2", id="mapping ampersand"), pytest.param([("&", "=")], "?%26=%3D", id="tuple list quote key"), pytest.param({"&": "="}, "?%26=%3D", id="mapping quote key"), + pytest.param([("a[]", "3")], "?a%5B%5D=3", id="quote one key braces",), + pytest.param( + [("a[]", "3"), ("a[]", "4")], + "?a%5B%5D=3&a%5B%5D=4", + id="quote many key braces", + ), ], ) def test_with_query_params(query, expected):