test_update_query.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import enum
  2. import pytest
  3. from multidict import MultiDict
  4. from yarl import URL
  5. # with_query
  6. def test_with_query():
  7. url = URL("http://example.com")
  8. assert str(url.with_query({"a": "1"})) == "http://example.com/?a=1"
  9. def test_update_query():
  10. url = URL("http://example.com/")
  11. assert str(url.update_query({"a": "1"})) == "http://example.com/?a=1"
  12. assert str(URL("test").update_query(a=1)) == "test?a=1"
  13. url = URL("http://example.com/?foo=bar")
  14. expected_url = URL("http://example.com/?foo=bar&baz=foo")
  15. assert url.update_query({"baz": "foo"}) == expected_url
  16. assert url.update_query(baz="foo") == expected_url
  17. assert url.update_query("baz=foo") == expected_url
  18. def test_update_query_with_args_and_kwargs():
  19. url = URL("http://example.com/")
  20. with pytest.raises(ValueError):
  21. url.update_query("a", foo="bar")
  22. def test_update_query_with_multiple_args():
  23. url = URL("http://example.com/")
  24. with pytest.raises(ValueError):
  25. url.update_query("a", "b")
  26. def test_update_query_with_none_arg():
  27. url = URL("http://example.com/?foo=bar&baz=foo")
  28. expected_url = URL("http://example.com/")
  29. assert url.update_query(None) == expected_url
  30. def test_update_query_with_empty_dict():
  31. url = URL("http://example.com/?foo=bar&baz=foo")
  32. assert url.update_query({}) == url
  33. def test_with_query_list_of_pairs():
  34. url = URL("http://example.com")
  35. assert str(url.with_query([("a", "1")])) == "http://example.com/?a=1"
  36. def test_with_query_list_non_pairs():
  37. url = URL("http://example.com")
  38. with pytest.raises(ValueError):
  39. url.with_query(["a=1", "b=2", "c=3"])
  40. def test_with_query_kwargs():
  41. url = URL("http://example.com")
  42. q = url.with_query(query="1", query2="1").query
  43. assert q == dict(query="1", query2="1")
  44. def test_with_query_kwargs_and_args_are_mutually_exclusive():
  45. url = URL("http://example.com")
  46. with pytest.raises(ValueError):
  47. url.with_query({"a": "2", "b": "4"}, a="1")
  48. def test_with_query_only_single_arg_is_supported():
  49. url = URL("http://example.com")
  50. u1 = url.with_query(b=3)
  51. u2 = URL("http://example.com/?b=3")
  52. assert u1 == u2
  53. with pytest.raises(ValueError):
  54. url.with_query("a=1", "a=b")
  55. def test_with_query_empty_dict():
  56. url = URL("http://example.com/?a=b")
  57. new_url = url.with_query({})
  58. assert new_url.query_string == ""
  59. assert str(new_url) == "http://example.com/"
  60. def test_with_query_empty_str():
  61. url = URL("http://example.com/?a=b")
  62. assert str(url.with_query("")) == "http://example.com/"
  63. def test_with_query_empty_value():
  64. url = URL("http://example.com/")
  65. assert str(url.with_query({"a": ""})) == "http://example.com/?a="
  66. def test_with_query_str():
  67. url = URL("http://example.com")
  68. assert str(url.with_query("a=1&b=2")) == "http://example.com/?a=1&b=2"
  69. def test_with_query_str_non_ascii_and_spaces():
  70. url = URL("http://example.com")
  71. url2 = url.with_query("a=1 2&b=знач")
  72. assert url2.raw_query_string == "a=1+2&b=%D0%B7%D0%BD%D0%B0%D1%87"
  73. assert url2.query_string == "a=1 2&b=знач"
  74. def test_with_query_int():
  75. url = URL("http://example.com")
  76. assert url.with_query({"a": 1}) == URL("http://example.com/?a=1")
  77. def test_with_query_kwargs_int():
  78. url = URL("http://example.com")
  79. assert url.with_query(b=2) == URL("http://example.com/?b=2")
  80. def test_with_query_list_int():
  81. url = URL("http://example.com")
  82. assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1"
  83. @pytest.mark.parametrize(
  84. ("query", "expected"),
  85. [
  86. pytest.param({"a": []}, "", id="empty list"),
  87. pytest.param({"a": ()}, "", id="empty tuple"),
  88. pytest.param({"a": [1]}, "/?a=1", id="single list"),
  89. pytest.param({"a": (1,)}, "/?a=1", id="single tuple"),
  90. pytest.param({"a": [1, 2]}, "/?a=1&a=2", id="list"),
  91. pytest.param({"a": (1, 2)}, "/?a=1&a=2", id="tuple"),
  92. pytest.param({"a[]": [1, 2]}, "/?a%5B%5D=1&a%5B%5D=2", id="key with braces"),
  93. pytest.param({"&": [1, 2]}, "/?%26=1&%26=2", id="quote key"),
  94. pytest.param({"a": ["1", 2]}, "/?a=1&a=2", id="mixed types"),
  95. pytest.param({"&": ["=", 2]}, "/?%26=%3D&%26=2", id="quote key and value"),
  96. pytest.param({"a": 1, "b": [2, 3]}, "/?a=1&b=2&b=3", id="single then list"),
  97. pytest.param({"a": [1, 2], "b": 3}, "/?a=1&a=2&b=3", id="list then single"),
  98. pytest.param({"a": ["1&a=2", 3]}, "/?a=1%26a%3D2&a=3", id="ampersand then int"),
  99. pytest.param({"a": [1, "2&a=3"]}, "/?a=1&a=2%26a%3D3", id="int then ampersand"),
  100. ],
  101. )
  102. def test_with_query_sequence(query, expected):
  103. url = URL("http://example.com")
  104. expected = "http://example.com{expected}".format_map(locals())
  105. assert str(url.with_query(query)) == expected
  106. @pytest.mark.parametrize(
  107. "query",
  108. [
  109. pytest.param({"a": [[1]]}, id="nested"),
  110. pytest.param([("a", [1, 2])], id="tuple list"),
  111. ],
  112. )
  113. def test_with_query_sequence_invalid_use(query):
  114. url = URL("http://example.com")
  115. with pytest.raises(TypeError, match="Invalid variable type"):
  116. url.with_query(query)
  117. class _CStr(str):
  118. pass
  119. class _EmptyStrEr:
  120. def __str__(self):
  121. return "" # pragma: no cover # <-- this should never happen
  122. class _CInt(int, _EmptyStrEr):
  123. pass
  124. class _CFloat(float, _EmptyStrEr):
  125. pass
  126. @pytest.mark.parametrize(
  127. ("value", "expected"),
  128. [
  129. pytest.param("1", "1", id="str"),
  130. pytest.param(_CStr("1"), "1", id="custom str"),
  131. pytest.param(1, "1", id="int"),
  132. pytest.param(_CInt(1), "1", id="custom int"),
  133. pytest.param(1.1, "1.1", id="float"),
  134. pytest.param(_CFloat(1.1), "1.1", id="custom float"),
  135. ],
  136. )
  137. def test_with_query_valid_type(value, expected):
  138. url = URL("http://example.com")
  139. expected = "http://example.com/?a={expected}".format_map(locals())
  140. assert str(url.with_query({"a": value})) == expected
  141. @pytest.mark.parametrize(
  142. ("value", "exc_type"),
  143. [
  144. pytest.param(True, TypeError, id="bool"),
  145. pytest.param(None, TypeError, id="none"),
  146. pytest.param(float("inf"), ValueError, id="non-finite float"),
  147. pytest.param(float("nan"), ValueError, id="NaN float"),
  148. ],
  149. )
  150. def test_with_query_invalid_type(value, exc_type):
  151. url = URL("http://example.com")
  152. with pytest.raises(exc_type):
  153. url.with_query({"a": value})
  154. @pytest.mark.parametrize(
  155. ("value", "expected"),
  156. [
  157. pytest.param("1", "1", id="str"),
  158. pytest.param(_CStr("1"), "1", id="custom str"),
  159. pytest.param(1, "1", id="int"),
  160. pytest.param(_CInt(1), "1", id="custom int"),
  161. pytest.param(1.1, "1.1", id="float"),
  162. pytest.param(_CFloat(1.1), "1.1", id="custom float"),
  163. ],
  164. )
  165. def test_with_query_list_valid_type(value, expected):
  166. url = URL("http://example.com")
  167. expected = "http://example.com/?a={expected}".format_map(locals())
  168. assert str(url.with_query([("a", value)])) == expected
  169. @pytest.mark.parametrize(
  170. ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")]
  171. )
  172. def test_with_query_list_invalid_type(value):
  173. url = URL("http://example.com")
  174. with pytest.raises(TypeError):
  175. url.with_query([("a", value)])
  176. def test_with_int_enum():
  177. class IntEnum(int, enum.Enum):
  178. A = 1
  179. url = URL("http://example.com/path")
  180. url2 = url.with_query(a=IntEnum.A)
  181. assert str(url2) == "http://example.com/path?a=1"
  182. def test_with_float_enum():
  183. class FloatEnum(float, enum.Enum):
  184. A = 1.1
  185. url = URL("http://example.com/path")
  186. url2 = url.with_query(a=FloatEnum.A)
  187. assert str(url2) == "http://example.com/path?a=1.1"
  188. def test_with_query_multidict():
  189. url = URL("http://example.com/path")
  190. q = MultiDict([("a", "b"), ("c", "d")])
  191. assert str(url.with_query(q)) == "http://example.com/path?a=b&c=d"
  192. def test_with_multidict_with_spaces_and_non_ascii():
  193. url = URL("http://example.com")
  194. url2 = url.with_query({"a b": "ю б"})
  195. assert url2.raw_query_string == "a+b=%D1%8E+%D0%B1"
  196. def test_with_query_multidict_with_unsafe():
  197. url = URL("http://example.com/path")
  198. url2 = url.with_query({"a+b": "?=+&;"})
  199. assert url2.raw_query_string == "a%2Bb=?%3D%2B%26%3B"
  200. assert url2.query_string == "a%2Bb=?%3D%2B%26%3B"
  201. assert url2.query == {"a+b": "?=+&;"}
  202. def test_with_query_None():
  203. url = URL("http://example.com/path?a=b")
  204. assert url.with_query(None).query_string == ""
  205. def test_with_query_bad_type():
  206. url = URL("http://example.com")
  207. with pytest.raises(TypeError):
  208. url.with_query(123)
  209. def test_with_query_bytes():
  210. url = URL("http://example.com")
  211. with pytest.raises(TypeError):
  212. url.with_query(b"123")
  213. def test_with_query_bytearray():
  214. url = URL("http://example.com")
  215. with pytest.raises(TypeError):
  216. url.with_query(bytearray(b"123"))
  217. def test_with_query_memoryview():
  218. url = URL("http://example.com")
  219. with pytest.raises(TypeError):
  220. url.with_query(memoryview(b"123"))
  221. @pytest.mark.parametrize(
  222. ("query", "expected"),
  223. [
  224. pytest.param([("key", "1;2;3")], "?key=1%3B2%3B3", id="tuple list semicolon"),
  225. pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"),
  226. pytest.param([("key", "1&a=2")], "?key=1%26a%3D2", id="tuple list ampersand"),
  227. pytest.param({"key": "1&a=2"}, "?key=1%26a%3D2", id="mapping ampersand"),
  228. pytest.param([("&", "=")], "?%26=%3D", id="tuple list quote key"),
  229. pytest.param({"&": "="}, "?%26=%3D", id="mapping quote key"),
  230. pytest.param(
  231. [("a[]", "3")],
  232. "?a%5B%5D=3",
  233. id="quote one key braces",
  234. ),
  235. pytest.param(
  236. [("a[]", "3"), ("a[]", "4")],
  237. "?a%5B%5D=3&a%5B%5D=4",
  238. id="quote many key braces",
  239. ),
  240. ],
  241. )
  242. def test_with_query_params(query, expected):
  243. url = URL("http://example.com/get")
  244. url2 = url.with_query(query)
  245. assert str(url2) == ("http://example.com/get" + expected)
  246. def test_with_query_only():
  247. url = URL()
  248. url2 = url.with_query(key="value")
  249. assert str(url2) == "?key=value"
  250. def test_with_query_complex_url():
  251. target_url = "http://example.com/?game=bulls+%26+cows"
  252. url = URL("/redir").with_query({"t": target_url})
  253. assert url.query["t"] == target_url
  254. def test_update_query_multiple_keys():
  255. url = URL("http://example.com/path?a=1&a=2")
  256. u2 = url.update_query([("a", "3"), ("a", "4")])
  257. assert str(u2) == "http://example.com/path?a=3&a=4"
  258. # mod operator
  259. def test_update_query_with_mod_operator():
  260. url = URL("http://example.com/")
  261. assert str(url % {"a": "1"}) == "http://example.com/?a=1"
  262. assert str(url % [("a", "1")]) == "http://example.com/?a=1"
  263. assert str(url % "a=1&b=2") == "http://example.com/?a=1&b=2"
  264. assert str(url % {"a": "1"} % {"b": "2"}) == "http://example.com/?a=1&b=2"
  265. assert str(url % {"a": "1"} % {"a": "3", "b": "2"}) == "http://example.com/?a=3&b=2"
  266. assert str(url / "foo" % {"a": "1"}) == "http://example.com/foo?a=1"