test_multidict.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. import gc
  2. import operator
  3. import sys
  4. import weakref
  5. from collections import deque
  6. from collections.abc import Mapping
  7. from functools import reduce
  8. from typing import (
  9. Any,
  10. Callable,
  11. Dict,
  12. Iterable,
  13. Iterator,
  14. List,
  15. Mapping,
  16. Set,
  17. Tuple,
  18. Type,
  19. TypeVar,
  20. Union,
  21. )
  22. import pytest
  23. import multidict
  24. from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy
  25. _MultiDictClasses = Union[Type[MultiDict[str]], Type[CIMultiDict[str]]]
  26. def chained_callable(
  27. module: object, callables: Union[str, Iterable[str]]
  28. ) -> Callable[..., Any]:
  29. """
  30. Returns callable that will get and call all given objects in module in
  31. exact order. If `names` is a single object's name function will return
  32. object itself.
  33. Will treat `names` of type `str` as a list of single element.
  34. """
  35. callables = (callables,) if isinstance(callables, str) else callables
  36. _callable, *rest = (getattr(module, name) for name in callables)
  37. def chained_call(*args: object, **kwargs: object) -> Any:
  38. return reduce(lambda res, c: c(res), rest, _callable(*args, **kwargs))
  39. return chained_call if len(rest) > 0 else _callable # type: ignore[no-any-return]
  40. @pytest.fixture(scope="function")
  41. def cls(request: Any, _multidict: Any) -> Any:
  42. return chained_callable(_multidict, request.param)
  43. @pytest.fixture(scope="function")
  44. def classes(request: Any, _multidict: Any) -> Any:
  45. return tuple(chained_callable(_multidict, n) for n in request.param)
  46. @pytest.mark.parametrize("cls", ["MultiDict", "CIMultiDict"], indirect=True)
  47. def test_exposed_names(
  48. cls: Union[Type[MultiDict[object]], Type[CIMultiDict[object]]]
  49. ) -> None:
  50. name = cls.__name__
  51. while name.startswith("_"):
  52. name = name[1:]
  53. assert name in multidict.__all__ # type: ignore[attr-defined]
  54. @pytest.mark.parametrize(
  55. "cls, key_cls",
  56. [("MultiDict", str), (("MultiDict", "MultiDictProxy"), str)],
  57. indirect=["cls"],
  58. )
  59. def test__iter__types(
  60. cls: Type[MultiDict[Union[str, int]]], key_cls: Type[object]
  61. ) -> None:
  62. d = cls([("key", "one"), ("key2", "two"), ("key", 3)])
  63. for i in d:
  64. assert type(i) is key_cls, (type(i), key_cls)
  65. _ClsPair = TypeVar(
  66. "_ClsPair",
  67. Tuple[Type[MultiDict[str]], Type[MultiDictProxy[str]]],
  68. Tuple[Type[CIMultiDict[str]], Type[CIMultiDictProxy[str]]],
  69. )
  70. @pytest.mark.parametrize(
  71. "classes",
  72. [("MultiDict", "MultiDictProxy"), ("CIMultiDict", "CIMultiDictProxy")],
  73. indirect=True,
  74. )
  75. def test_proxy_copy(classes: _ClsPair) -> None:
  76. dict_cls, proxy_cls = classes
  77. d1 = dict_cls(key="value", a="b")
  78. p1 = proxy_cls(d1)
  79. d2 = p1.copy()
  80. assert d1 == d2
  81. assert d1 is not d2
  82. @pytest.mark.parametrize(
  83. "cls",
  84. ["MultiDict", "CIMultiDict", "MultiDictProxy", "CIMultiDictProxy"],
  85. indirect=True,
  86. )
  87. def test_subclassing(cls: Any) -> None:
  88. class MyClass(cls): # type: ignore[valid-type,misc]
  89. pass
  90. class BaseMultiDictTest:
  91. def test_instantiate__empty(self, cls: _MultiDictClasses) -> None:
  92. d = cls()
  93. empty: Mapping[str, str] = {}
  94. assert d == empty
  95. assert len(d) == 0
  96. assert list(d.keys()) == []
  97. assert list(d.values()) == []
  98. assert list(d.items()) == []
  99. assert cls() != list() # type: ignore[comparison-overlap]
  100. with pytest.raises(TypeError, match=r"(2 given)"):
  101. cls(("key1", "value1"), ("key2", "value2")) # type: ignore[arg-type,call-arg] # noqa: E501
  102. @pytest.mark.parametrize("arg0", [[("key", "value1")], {"key": "value1"}])
  103. def test_instantiate__from_arg0(
  104. self,
  105. cls: _MultiDictClasses,
  106. arg0: Union[List[Tuple[str, str]], Dict[str, str]],
  107. ) -> None:
  108. d = cls(arg0)
  109. assert d == {"key": "value1"}
  110. assert len(d) == 1
  111. assert list(d.keys()) == ["key"]
  112. assert list(d.values()) == ["value1"]
  113. assert list(d.items()) == [("key", "value1")]
  114. def test_instantiate__with_kwargs(self, cls: _MultiDictClasses) -> None:
  115. d = cls([("key", "value1")], key2="value2")
  116. assert d == {"key": "value1", "key2": "value2"}
  117. assert len(d) == 2
  118. assert sorted(d.keys()) == ["key", "key2"]
  119. assert sorted(d.values()) == ["value1", "value2"]
  120. assert sorted(d.items()) == [("key", "value1"), ("key2", "value2")]
  121. def test_instantiate__from_generator(
  122. self, cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  123. ) -> None:
  124. d = cls((str(i), i) for i in range(2))
  125. assert d == {"0": 0, "1": 1}
  126. assert len(d) == 2
  127. assert sorted(d.keys()) == ["0", "1"]
  128. assert sorted(d.values()) == [0, 1]
  129. assert sorted(d.items()) == [("0", 0), ("1", 1)]
  130. def test_instantiate__from_list_of_lists(self, cls: _MultiDictClasses) -> None:
  131. # Should work at runtime, but won't type check.
  132. d = cls([["key", "value1"]]) # type: ignore[list-item]
  133. assert d == {"key": "value1"}
  134. def test_instantiate__from_list_of_custom_pairs(
  135. self, cls: _MultiDictClasses
  136. ) -> None:
  137. class Pair:
  138. def __len__(self) -> int:
  139. return 2
  140. def __getitem__(self, pos: int) -> str:
  141. if pos == 0:
  142. return "key"
  143. elif pos == 1:
  144. return "value1"
  145. else:
  146. raise IndexError
  147. # Works at runtime, but won't type check.
  148. d = cls([Pair()]) # type: ignore[list-item]
  149. assert d == {"key": "value1"}
  150. def test_getone(self, cls: _MultiDictClasses) -> None:
  151. d = cls([("key", "value1")], key="value2")
  152. assert d.getone("key") == "value1"
  153. assert d.get("key") == "value1"
  154. assert d["key"] == "value1"
  155. with pytest.raises(KeyError, match="key2"):
  156. d["key2"]
  157. with pytest.raises(KeyError, match="key2"):
  158. d.getone("key2")
  159. assert d.getone("key2", "default") == "default"
  160. assert d.getone(key="key2", default="default") == "default"
  161. def test__iter__(
  162. self,
  163. cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]]
  164. ) -> None:
  165. d = cls([("key", "one"), ("key2", "two"), ("key", 3)])
  166. assert list(d) == ["key", "key2", "key"]
  167. def test_keys__contains(
  168. self,
  169. cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]]
  170. ) -> None:
  171. d = cls([("key", "one"), ("key2", "two"), ("key", 3)])
  172. assert list(d.keys()) == ["key", "key2", "key"]
  173. assert "key" in d.keys()
  174. assert "key2" in d.keys()
  175. assert "foo" not in d.keys()
  176. def test_values__contains(
  177. self,
  178. cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]]
  179. ) -> None:
  180. d = cls([("key", "one"), ("key", "two"), ("key", 3)])
  181. assert list(d.values()) == ["one", "two", 3]
  182. assert "one" in d.values()
  183. assert "two" in d.values()
  184. assert 3 in d.values()
  185. assert "foo" not in d.values()
  186. def test_items__contains(
  187. self,
  188. cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]]
  189. ) -> None:
  190. d = cls([("key", "one"), ("key", "two"), ("key", 3)])
  191. assert list(d.items()) == [("key", "one"), ("key", "two"), ("key", 3)]
  192. assert ("key", "one") in d.items()
  193. assert ("key", "two") in d.items()
  194. assert ("key", 3) in d.items()
  195. assert ("foo", "bar") not in d.items()
  196. def test_cannot_create_from_unaccepted(self, cls: _MultiDictClasses) -> None:
  197. with pytest.raises(TypeError):
  198. cls([(1, 2, 3)]) # type: ignore[list-item]
  199. def test_keys_is_set_less(self, cls: _MultiDictClasses) -> None:
  200. d = cls([("key", "value1")])
  201. assert d.keys() < {"key", "key2"}
  202. def test_keys_is_set_less_equal(self, cls: _MultiDictClasses) -> None:
  203. d = cls([("key", "value1")])
  204. assert d.keys() <= {"key"}
  205. def test_keys_is_set_equal(self, cls: _MultiDictClasses) -> None:
  206. d = cls([("key", "value1")])
  207. assert d.keys() == {"key"}
  208. def test_keys_is_set_greater(self, cls: _MultiDictClasses) -> None:
  209. d = cls([("key", "value1")])
  210. assert {"key", "key2"} > d.keys()
  211. def test_keys_is_set_greater_equal(self, cls: _MultiDictClasses) -> None:
  212. d = cls([("key", "value1")])
  213. assert {"key"} >= d.keys()
  214. def test_keys_is_set_not_equal(self, cls: _MultiDictClasses) -> None:
  215. d = cls([("key", "value1")])
  216. assert d.keys() != {"key2"}
  217. def test_eq(self, cls: _MultiDictClasses) -> None:
  218. d = cls([("key", "value1")])
  219. assert {"key": "value1"} == d
  220. def test_eq2(self, cls: _MultiDictClasses) -> None:
  221. d1 = cls([("key", "value1")])
  222. d2 = cls([("key2", "value1")])
  223. assert d1 != d2
  224. def test_eq3(self, cls: _MultiDictClasses) -> None:
  225. d1 = cls([("key", "value1")])
  226. d2 = cls()
  227. assert d1 != d2
  228. def test_eq_other_mapping_contains_more_keys(self, cls: _MultiDictClasses) -> None:
  229. d1 = cls(foo="bar")
  230. d2 = dict(foo="bar", bar="baz")
  231. assert d1 != d2
  232. def test_eq_bad_mapping_len(
  233. self, cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  234. ) -> None:
  235. class BadMapping(Mapping[str, int]):
  236. def __getitem__(self, key: str) -> int:
  237. return 1
  238. def __iter__(self) -> Iterator[str]:
  239. yield "a"
  240. def __len__(self) -> int: # type: ignore[return]
  241. 1 / 0
  242. d1 = cls(a=1)
  243. d2 = BadMapping()
  244. with pytest.raises(ZeroDivisionError):
  245. d1 == d2
  246. def test_eq_bad_mapping_getitem(
  247. self,
  248. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  249. ) -> None:
  250. class BadMapping(Mapping[str, int]):
  251. def __getitem__(self, key: str) -> int: # type: ignore[return]
  252. 1 / 0
  253. def __iter__(self) -> Iterator[str]:
  254. yield "a"
  255. def __len__(self) -> int:
  256. return 1
  257. d1 = cls(a=1)
  258. d2 = BadMapping()
  259. with pytest.raises(ZeroDivisionError):
  260. d1 == d2
  261. def test_ne(self, cls: _MultiDictClasses) -> None:
  262. d = cls([("key", "value1")])
  263. assert d != {"key": "another_value"}
  264. def test_and(self, cls: _MultiDictClasses) -> None:
  265. d = cls([("key", "value1")])
  266. assert {"key"} == d.keys() & {"key", "key2"}
  267. def test_and2(self, cls: _MultiDictClasses) -> None:
  268. d = cls([("key", "value1")])
  269. assert {"key"} == {"key", "key2"} & d.keys()
  270. def test_or(self, cls: _MultiDictClasses) -> None:
  271. d = cls([("key", "value1")])
  272. assert {"key", "key2"} == d.keys() | {"key2"}
  273. def test_or2(self, cls: _MultiDictClasses) -> None:
  274. d = cls([("key", "value1")])
  275. assert {"key", "key2"} == {"key2"} | d.keys()
  276. def test_sub(self, cls: _MultiDictClasses) -> None:
  277. d = cls([("key", "value1"), ("key2", "value2")])
  278. assert {"key"} == d.keys() - {"key2"}
  279. def test_sub2(self, cls: _MultiDictClasses) -> None:
  280. d = cls([("key", "value1"), ("key2", "value2")])
  281. assert {"key3"} == {"key", "key2", "key3"} - d.keys()
  282. def test_xor(self, cls: _MultiDictClasses) -> None:
  283. d = cls([("key", "value1"), ("key2", "value2")])
  284. assert {"key", "key3"} == d.keys() ^ {"key2", "key3"}
  285. def test_xor2(self, cls: _MultiDictClasses) -> None:
  286. d = cls([("key", "value1"), ("key2", "value2")])
  287. assert {"key", "key3"} == {"key2", "key3"} ^ d.keys()
  288. @pytest.mark.parametrize("_set, expected", [({"key2"}, True), ({"key"}, False)])
  289. def test_isdisjoint(
  290. self, cls: _MultiDictClasses, _set: Set[str], expected: bool
  291. ) -> None:
  292. d = cls([("key", "value1")])
  293. assert d.keys().isdisjoint(_set) == expected
  294. def test_repr_issue_410(self, cls: _MultiDictClasses) -> None:
  295. d = cls()
  296. try:
  297. raise Exception
  298. pytest.fail("Should never happen") # pragma: no cover
  299. except Exception as e:
  300. repr(d)
  301. assert sys.exc_info()[1] == e
  302. @pytest.mark.parametrize(
  303. "op", [operator.or_, operator.and_, operator.sub, operator.xor]
  304. )
  305. @pytest.mark.parametrize("other", [{"other"}])
  306. def test_op_issue_410(
  307. self,
  308. cls: _MultiDictClasses,
  309. op: Callable[[object, object], object],
  310. other: Set[str],
  311. ) -> None:
  312. d = cls([("key", "value")])
  313. try:
  314. raise Exception
  315. pytest.fail("Should never happen") # pragma: no cover
  316. except Exception as e:
  317. op(d.keys(), other)
  318. assert sys.exc_info()[1] == e
  319. def test_weakref(self, cls: _MultiDictClasses) -> None:
  320. called = False
  321. def cb(wr: object) -> None:
  322. nonlocal called
  323. called = True
  324. d = cls()
  325. wr = weakref.ref(d, cb)
  326. del d
  327. gc.collect()
  328. assert called
  329. del wr
  330. def test_iter_length_hint_keys(
  331. self,
  332. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  333. ) -> None:
  334. md = cls(a=1, b=2)
  335. it = iter(md.keys())
  336. assert it.__length_hint__() == 2 # type: ignore[attr-defined]
  337. def test_iter_length_hint_items(
  338. self,
  339. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  340. ) -> None:
  341. md = cls(a=1, b=2)
  342. it = iter(md.items())
  343. assert it.__length_hint__() == 2 # type: ignore[attr-defined]
  344. def test_iter_length_hint_values(
  345. self,
  346. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  347. ) -> None:
  348. md = cls(a=1, b=2)
  349. it = iter(md.values())
  350. assert it.__length_hint__() == 2 # type: ignore[attr-defined]
  351. def test_ctor_list_arg_and_kwds(
  352. self,
  353. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  354. ) -> None:
  355. arg = [("a", 1)]
  356. obj = cls(arg, b=2)
  357. assert list(obj.items()) == [("a", 1), ("b", 2)]
  358. assert arg == [("a", 1)]
  359. def test_ctor_tuple_arg_and_kwds(
  360. self,
  361. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  362. ) -> None:
  363. arg = (("a", 1),)
  364. obj = cls(arg, b=2)
  365. assert list(obj.items()) == [("a", 1), ("b", 2)]
  366. assert arg == (("a", 1),)
  367. def test_ctor_deque_arg_and_kwds(
  368. self,
  369. cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]]
  370. ) -> None:
  371. arg = deque([("a", 1)])
  372. obj = cls(arg, b=2)
  373. assert list(obj.items()) == [("a", 1), ("b", 2)]
  374. assert arg == deque([("a", 1)])
  375. class TestMultiDict(BaseMultiDictTest):
  376. @pytest.fixture(params=["MultiDict", ("MultiDict", "MultiDictProxy")])
  377. def cls(self, request: Any, _multidict: Any) -> Any:
  378. return chained_callable(_multidict, request.param)
  379. def test__repr__(self, cls: Type[MultiDict[str]]) -> None:
  380. d = cls()
  381. _cls = type(d)
  382. assert str(d) == "<%s()>" % _cls.__name__
  383. d = cls([("key", "one"), ("key", "two")])
  384. assert str(d) == "<%s('key': 'one', 'key': 'two')>" % _cls.__name__
  385. def test_getall(self, cls: Type[MultiDict[str]]) -> None:
  386. d = cls([("key", "value1")], key="value2")
  387. assert d != {"key": "value1"}
  388. assert len(d) == 2
  389. assert d.getall("key") == ["value1", "value2"]
  390. with pytest.raises(KeyError, match="some_key"):
  391. d.getall("some_key")
  392. default = object()
  393. assert d.getall("some_key", default) is default
  394. def test_preserve_stable_ordering(
  395. self, cls: Type[MultiDict[Union[str, int]]]
  396. ) -> None:
  397. d = cls([("a", 1), ("b", "2"), ("a", 3)])
  398. s = "&".join("{}={}".format(k, v) for k, v in d.items())
  399. assert s == "a=1&b=2&a=3"
  400. def test_get(self, cls: Type[MultiDict[int]]) -> None:
  401. d = cls([("a", 1), ("a", 2)])
  402. assert d["a"] == 1
  403. assert d.get("a") == 1
  404. assert d.get("z", 3) == 3
  405. def test_items__repr__(self, cls: Type[MultiDict[str]]) -> None:
  406. d = cls([("key", "value1")], key="value2")
  407. expected = "_ItemsView('key': 'value1', 'key': 'value2')"
  408. assert repr(d.items()) == expected
  409. def test_keys__repr__(self, cls: Type[MultiDict[str]]) -> None:
  410. d = cls([("key", "value1")], key="value2")
  411. assert repr(d.keys()) == "_KeysView('key', 'key')"
  412. def test_values__repr__(self, cls: Type[MultiDict[str]]) -> None:
  413. d = cls([("key", "value1")], key="value2")
  414. assert repr(d.values()) == "_ValuesView('value1', 'value2')"
  415. class TestCIMultiDict(BaseMultiDictTest):
  416. @pytest.fixture(params=["CIMultiDict", ("CIMultiDict", "CIMultiDictProxy")])
  417. def cls(self, request: Any, _multidict: Any) -> Any:
  418. return chained_callable(_multidict, request.param)
  419. def test_basics(self, cls: Type[CIMultiDict[str]]) -> None:
  420. d = cls([("KEY", "value1")], KEY="value2")
  421. assert d.getone("key") == "value1"
  422. assert d.get("key") == "value1"
  423. assert d.get("key2", "val") == "val"
  424. assert d["key"] == "value1"
  425. assert "key" in d
  426. with pytest.raises(KeyError, match="key2"):
  427. d["key2"]
  428. with pytest.raises(KeyError, match="key2"):
  429. d.getone("key2")
  430. def test_getall(self, cls: Type[CIMultiDict[str]]) -> None:
  431. d = cls([("KEY", "value1")], KEY="value2")
  432. assert not d == {"KEY": "value1"}
  433. assert len(d) == 2
  434. assert d.getall("key") == ["value1", "value2"]
  435. with pytest.raises(KeyError, match="some_key"):
  436. d.getall("some_key")
  437. def test_get(self, cls: Type[CIMultiDict[int]]) -> None:
  438. d = cls([("A", 1), ("a", 2)])
  439. assert 1 == d["a"]
  440. def test__repr__(self, cls: Type[CIMultiDict[str]]) -> None:
  441. d = cls([("KEY", "value1")], key="value2")
  442. _cls = type(d)
  443. expected = "<%s('KEY': 'value1', 'key': 'value2')>" % _cls.__name__
  444. assert str(d) == expected
  445. def test_items__repr__(self, cls: Type[CIMultiDict[str]]) -> None:
  446. d = cls([("KEY", "value1")], key="value2")
  447. expected = "_ItemsView('KEY': 'value1', 'key': 'value2')"
  448. assert repr(d.items()) == expected
  449. def test_keys__repr__(self, cls: Type[CIMultiDict[str]]) -> None:
  450. d = cls([("KEY", "value1")], key="value2")
  451. assert repr(d.keys()) == "_KeysView('KEY', 'key')"
  452. def test_values__repr__(self, cls: Type[CIMultiDict[str]]) -> None:
  453. d = cls([("KEY", "value1")], key="value2")
  454. assert repr(d.values()) == "_ValuesView('value1', 'value2')"