From 774cb40a1750723f6ad6fa8fb778978b41227317 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 22 Jan 2019 17:29:39 +0000 Subject: [PATCH] Ensure query params behavior matches standard mappings. (#338) * QueryParams behavior as standard mapping --- starlette/datastructures.py | 21 +++++++++++++-------- tests/test_datastructures.py | 21 ++++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/starlette/datastructures.py b/starlette/datastructures.py index 510dbcd1..1ce3aab9 100644 --- a/starlette/datastructures.py +++ b/starlette/datastructures.py @@ -231,7 +231,7 @@ class QueryParams(typing.Mapping[str, str]): def __init__( self, - params: typing.Mapping[str, str] = None, + params: typing.Union["QueryParams", typing.Mapping[str, str]] = None, items: typing.List[typing.Tuple[str, str]] = None, query_string: str = None, scope: Scope = None, @@ -241,7 +241,10 @@ class QueryParams(typing.Mapping[str, str]): assert items is None, "Cannot set both 'params' and 'items'" assert query_string is None, "Cannot set both 'params' and 'query_string'" assert scope is None, "Cannot set both 'params' and 'scope'" - _items = list(params.items()) + if isinstance(params, QueryParams): + _items = list(params.multi_items()) + else: + _items = list(params.items()) elif items is not None: assert query_string is None, "Cannot set both 'items' and 'query_string'" assert scope is None, "Cannot set both 'items' and 'scope'" @@ -252,26 +255,28 @@ class QueryParams(typing.Mapping[str, str]): elif scope is not None: _items = parse_qsl(scope["query_string"].decode("latin-1")) - self._dict = {k: v for k, v in reversed(_items)} + self._dict = {k: v for k, v in _items} self._list = _items def getlist(self, key: typing.Any) -> typing.List[str]: return [item_value for item_key, item_value in self._list if item_key == key] def keys(self) -> typing.List[str]: # type: ignore - return [key for key, value in self._list] + return list(self._dict.keys()) def values(self) -> typing.List[str]: # type: ignore - return [value for key, value in self._list] + return list(self._dict.values()) def items(self) -> typing.List[typing.Tuple[str, str]]: # type: ignore + return list(self._dict.items()) + + def multi_items(self) -> typing.List[typing.Tuple[str, str]]: return list(self._list) def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any: if key in self._dict: return self._dict[key] - else: - return default + return default def __getitem__(self, key: typing.Any) -> str: return self._dict[key] @@ -283,7 +288,7 @@ class QueryParams(typing.Mapping[str, str]): return iter(self.keys()) def __len__(self) -> int: - return len(self._list) + return len(self._dict) def __eq__(self, other: typing.Any) -> bool: if not isinstance(other, QueryParams): diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 4f873539..51c9ba8e 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -172,15 +172,16 @@ def test_queryparams(): assert "a" in q assert "A" not in q assert "c" not in q - assert q["a"] == "123" - assert q.get("a") == "123" + assert q["a"] == "456" + assert q.get("a") == "456" assert q.get("nope", default=None) is None assert q.getlist("a") == ["123", "456"] - assert q.keys() == ["a", "a", "b"] - assert q.values() == ["123", "456", "789"] - assert q.items() == [("a", "123"), ("a", "456"), ("b", "789")] - assert list(q) == ["a", "a", "b"] - assert dict(q) == {"a": "123", "b": "789"} + assert q.keys() == ["a", "b"] + assert q.values() == ["456", "789"] + assert q.items() == [("a", "456"), ("b", "789")] + assert len(q) == 2 + assert list(q) == ["a", "b"] + assert dict(q) == {"a": "456", "b": "789"} assert str(q) == "a=123&a=456&b=789" assert repr(q) == "QueryParams(query_string='a=123&a=456&b=789')" assert QueryParams({"a": "123", "b": "456"}) == QueryParams( @@ -193,4 +194,10 @@ def test_queryparams(): {"b": "456", "a": "123"} ) assert QueryParams() == QueryParams({}) + assert QueryParams(items=[("a", "123"), ("a", "456")]) == QueryParams( + query_string="a=123&a=456" + ) assert QueryParams({"a": "123", "b": "456"}) != "invalid" + + q = QueryParams(items=[("a", "123"), ("a", "456")]) + assert QueryParams(q) == q