import enum import pytest from multidict import MultiDict from yarl import URL # with_query def test_with_query(): url = URL("http://example.com") assert str(url.with_query({"a": "1"})) == "http://example.com/?a=1" def test_update_query(): url = URL("http://example.com/") assert str(url.update_query({"a": "1"})) == "http://example.com/?a=1" assert str(URL("test").update_query(a=1)) == "test?a=1" url = URL("http://example.com/?foo=bar") expected_url = URL("http://example.com/?foo=bar&baz=foo") assert url.update_query({"baz": "foo"}) == expected_url assert url.update_query(baz="foo") == expected_url assert url.update_query("baz=foo") == expected_url def test_update_query_with_args_and_kwargs(): url = URL("http://example.com/") with pytest.raises(ValueError): url.update_query("a", foo="bar") def test_update_query_with_multiple_args(): url = URL("http://example.com/") with pytest.raises(ValueError): url.update_query("a", "b") def test_update_query_with_none_arg(): url = URL("http://example.com/?foo=bar&baz=foo") expected_url = URL("http://example.com/") assert url.update_query(None) == expected_url def test_update_query_with_empty_dict(): url = URL("http://example.com/?foo=bar&baz=foo") assert url.update_query({}) == url def test_with_query_list_of_pairs(): url = URL("http://example.com") assert str(url.with_query([("a", "1")])) == "http://example.com/?a=1" def test_with_query_list_non_pairs(): url = URL("http://example.com") with pytest.raises(ValueError): url.with_query(["a=1", "b=2", "c=3"]) def test_with_query_kwargs(): url = URL("http://example.com") q = url.with_query(query="1", query2="1").query assert q == dict(query="1", query2="1") def test_with_query_kwargs_and_args_are_mutually_exclusive(): url = URL("http://example.com") with pytest.raises(ValueError): url.with_query({"a": "2", "b": "4"}, a="1") def test_with_query_only_single_arg_is_supported(): url = URL("http://example.com") u1 = url.with_query(b=3) u2 = URL("http://example.com/?b=3") assert u1 == u2 with pytest.raises(ValueError): url.with_query("a=1", "a=b") def test_with_query_empty_dict(): url = URL("http://example.com/?a=b") new_url = url.with_query({}) assert new_url.query_string == "" assert str(new_url) == "http://example.com/" def test_with_query_empty_str(): url = URL("http://example.com/?a=b") assert str(url.with_query("")) == "http://example.com/" def test_with_query_empty_value(): url = URL("http://example.com/") assert str(url.with_query({"a": ""})) == "http://example.com/?a=" def test_with_query_str(): url = URL("http://example.com") assert str(url.with_query("a=1&b=2")) == "http://example.com/?a=1&b=2" def test_with_query_str_non_ascii_and_spaces(): url = URL("http://example.com") url2 = url.with_query("a=1 2&b=знач") assert url2.raw_query_string == "a=1+2&b=%D0%B7%D0%BD%D0%B0%D1%87" assert url2.query_string == "a=1 2&b=знач" def test_with_query_int(): url = URL("http://example.com") assert url.with_query({"a": 1}) == URL("http://example.com/?a=1") def test_with_query_kwargs_int(): url = URL("http://example.com") assert url.with_query(b=2) == URL("http://example.com/?b=2") def test_with_query_list_int(): url = URL("http://example.com") assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1" @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%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"), 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") expected = "http://example.com{expected}".format_map(locals()) assert str(url.with_query(query)) == expected @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, match="Invalid variable type"): url.with_query(query) class _CStr(str): pass class _EmptyStrEr: def __str__(self): return "" # pragma: no cover # <-- this should never happen class _CInt(int, _EmptyStrEr): pass class _CFloat(float, _EmptyStrEr): pass @pytest.mark.parametrize( ("value", "expected"), [ pytest.param("1", "1", id="str"), pytest.param(_CStr("1"), "1", id="custom str"), pytest.param(1, "1", id="int"), pytest.param(_CInt(1), "1", id="custom int"), pytest.param(1.1, "1.1", id="float"), pytest.param(_CFloat(1.1), "1.1", id="custom float"), ], ) def test_with_query_valid_type(value, expected): url = URL("http://example.com") expected = "http://example.com/?a={expected}".format_map(locals()) assert str(url.with_query({"a": value})) == expected @pytest.mark.parametrize( ("value", "exc_type"), [ pytest.param(True, TypeError, id="bool"), pytest.param(None, TypeError, id="none"), pytest.param(float("inf"), ValueError, id="non-finite float"), pytest.param(float("nan"), ValueError, id="NaN float"), ], ) def test_with_query_invalid_type(value, exc_type): url = URL("http://example.com") with pytest.raises(exc_type): url.with_query({"a": value}) @pytest.mark.parametrize( ("value", "expected"), [ pytest.param("1", "1", id="str"), pytest.param(_CStr("1"), "1", id="custom str"), pytest.param(1, "1", id="int"), pytest.param(_CInt(1), "1", id="custom int"), pytest.param(1.1, "1.1", id="float"), pytest.param(_CFloat(1.1), "1.1", id="custom float"), ], ) def test_with_query_list_valid_type(value, expected): url = URL("http://example.com") expected = "http://example.com/?a={expected}".format_map(locals()) assert str(url.with_query([("a", value)])) == expected @pytest.mark.parametrize( ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")] ) def test_with_query_list_invalid_type(value): url = URL("http://example.com") with pytest.raises(TypeError): url.with_query([("a", value)]) def test_with_int_enum(): class IntEnum(int, enum.Enum): A = 1 url = URL("http://example.com/path") url2 = url.with_query(a=IntEnum.A) assert str(url2) == "http://example.com/path?a=1" def test_with_class_that_implements__int__(): """Allow classes that implement __int__ to be used in query strings.""" class myint: def __int__(self): return 84 url = URL("http://example.com/path") url2 = url.with_query(a=myint()) assert str(url2) == "http://example.com/path?a=84" def test_with_float_enum(): class FloatEnum(float, enum.Enum): A = 1.1 url = URL("http://example.com/path") url2 = url.with_query(a=FloatEnum.A) assert str(url2) == "http://example.com/path?a=1.1" def test_with_query_multidict(): url = URL("http://example.com/path") q = MultiDict([("a", "b"), ("c", "d")]) assert str(url.with_query(q)) == "http://example.com/path?a=b&c=d" def test_with_multidict_with_spaces_and_non_ascii(): url = URL("http://example.com") url2 = url.with_query({"a b": "ю б"}) assert url2.raw_query_string == "a+b=%D1%8E+%D0%B1" def test_with_query_multidict_with_unsafe(): url = URL("http://example.com/path") url2 = url.with_query({"a+b": "?=+&;"}) assert url2.raw_query_string == "a%2Bb=?%3D%2B%26%3B" assert url2.query_string == "a%2Bb=?%3D%2B%26%3B" assert url2.query == {"a+b": "?=+&;"} def test_with_query_None(): url = URL("http://example.com/path?a=b") assert url.with_query(None).query_string == "" def test_with_query_bad_type(): url = URL("http://example.com") with pytest.raises(TypeError): url.with_query(123) def test_with_query_bytes(): url = URL("http://example.com") with pytest.raises(TypeError): url.with_query(b"123") def test_with_query_bytearray(): url = URL("http://example.com") with pytest.raises(TypeError): url.with_query(bytearray(b"123")) def test_with_query_memoryview(): url = URL("http://example.com") with pytest.raises(TypeError): url.with_query(memoryview(b"123")) @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"), 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): url = URL("http://example.com/get") url2 = url.with_query(query) assert str(url2) == ("http://example.com/get" + expected) def test_with_query_only(): url = URL() url2 = url.with_query(key="value") assert str(url2) == "?key=value" def test_with_query_complex_url(): target_url = "http://example.com/?game=bulls+%26+cows" url = URL("/redir").with_query({"t": target_url}) assert url.query["t"] == target_url def test_update_query_multiple_keys(): url = URL("http://example.com/path?a=1&a=2") u2 = url.update_query([("a", "3"), ("a", "4")]) assert str(u2) == "http://example.com/path?a=3&a=4" def test_update_query_with_non_ascii(): url = URL("http://example.com/?foo=bar&baz=foo&%F0%9D%95%A6=%F0%9D%95%A6") assert url.update_query({"𝕦": "𝕦"}) == url def test_update_query_with_non_ascii_as_str(): url = URL("http://example.com/?foo=bar&baz=foo&%F0%9D%95%A6=%F0%9D%95%A6") assert url.update_query("𝕦=𝕦") == url # mod operator def test_update_query_with_mod_operator(): url = URL("http://example.com/") assert str(url % {"a": "1"}) == "http://example.com/?a=1" assert str(url % [("a", "1")]) == "http://example.com/?a=1" assert str(url % "a=1&b=2") == "http://example.com/?a=1&b=2" assert str(url % {"a": "1"} % {"b": "2"}) == "http://example.com/?a=1&b=2" assert str(url % {"a": "1"} % {"a": "3", "b": "2"}) == "http://example.com/?a=3&b=2" assert str(url / "foo" % {"a": "1"}) == "http://example.com/foo?a=1" def test_extend_query(): url = URL("http://example.com/") assert str(url.extend_query({"a": "1"})) == "http://example.com/?a=1" assert str(URL("test").extend_query(a=1)) == "test?a=1" url = URL("http://example.com/?foo=bar") expected_url = URL("http://example.com/?foo=bar&baz=foo") assert url.extend_query({"baz": "foo"}) == expected_url assert url.extend_query(baz="foo") == expected_url assert url.extend_query("baz=foo") == expected_url def test_extend_query_with_args_and_kwargs(): url = URL("http://example.com/") with pytest.raises(ValueError): url.extend_query("a", foo="bar") def test_extend_query_with_multiple_args(): url = URL("http://example.com/") with pytest.raises(ValueError): url.extend_query("a", "b") def test_extend_query_with_none_arg(): url = URL("http://example.com/?foo=bar&baz=foo") assert url.extend_query(None) == url def test_extend_query_with_empty_dict(): url = URL("http://example.com/?foo=bar&baz=foo") assert url.extend_query({}) == url def test_extend_query_existing_keys(): url = URL("http://example.com/?a=2") assert str(url.extend_query({"a": "1"})) == "http://example.com/?a=2&a=1" assert str(URL("test").extend_query(a=1)) == "test?a=1" url = URL("http://example.com/?foo=bar&baz=original") expected_url = URL("http://example.com/?foo=bar&baz=original&baz=foo") assert url.extend_query({"baz": "foo"}) == expected_url assert url.extend_query(baz="foo") == expected_url assert url.extend_query("baz=foo") == expected_url def test_extend_query_with_args_and_kwargs_with_existing(): url = URL("http://example.com/?a=original") with pytest.raises(ValueError): url.extend_query("a", foo="bar") def test_extend_query_with_non_ascii(): url = URL("http://example.com/?foo=bar&baz=foo") expected = URL("http://example.com/?foo=bar&baz=foo&%F0%9D%95%A6=%F0%9D%95%A6") assert url.extend_query({"𝕦": "𝕦"}) == expected def test_extend_query_with_non_ascii_as_str(): url = URL("http://example.com/?foo=bar&baz=foo&") expected = URL("http://example.com/?foo=bar&baz=foo&%F0%9D%95%A6=%F0%9D%95%A6") assert url.extend_query("𝕦=𝕦") == expected def test_extend_query_with_non_ascii_same_key(): url = URL("http://example.com/?foo=bar&baz=foo&%F0%9D%95%A6=%F0%9D%95%A6") expected = URL( "http://example.com/?foo=bar&baz=foo" "&%F0%9D%95%A6=%F0%9D%95%A6&%F0%9D%95%A6=%F0%9D%95%A6" ) assert url.extend_query({"𝕦": "𝕦"}) == expected