From 608d505b9981f31502c362c4d84ca120b1607123 Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 6 Nov 2022 22:53:28 +0100 Subject: [PATCH 1/2] added query multidict --- .../datastructures/7-form-multi-dict.md | 7 ---- .../reference/datastructures/7-multi-dicts.md | 13 ++++++ mkdocs.yml | 2 +- starlite/config/cache.py | 2 +- starlite/connection/base.py | 21 +++------- starlite/connection/request.py | 2 +- starlite/datastructures/__init__.py | 2 +- .../{form_multi_dict.py => multi_dicts.py} | 41 ++++++++++++++++++- starlite/kwargs.py | 4 +- starlite/parsers.py | 41 ++----------------- starlite/utils/extractors.py | 2 +- tests/connection/request/test_request.py | 2 +- tests/connection/websocket/test_websocket.py | 2 +- tests/datastructures/test_multi_dicts.py | 28 +++++++++++++ tests/test_parsers.py | 22 +--------- tests/testing/test_testing.py | 3 +- tests/utils/test_extractors.py | 4 +- 17 files changed, 105 insertions(+), 93 deletions(-) delete mode 100644 docs/reference/datastructures/7-form-multi-dict.md create mode 100644 docs/reference/datastructures/7-multi-dicts.md rename starlite/datastructures/{form_multi_dict.py => multi_dicts.py} (50%) create mode 100644 tests/datastructures/test_multi_dicts.py diff --git a/docs/reference/datastructures/7-form-multi-dict.md b/docs/reference/datastructures/7-form-multi-dict.md deleted file mode 100644 index 7ceb339f3e..0000000000 --- a/docs/reference/datastructures/7-form-multi-dict.md +++ /dev/null @@ -1,7 +0,0 @@ -# Form Multi-Dict - -::: starlite.datastructures.FormMultiDict - options: - members: - - close - - multi_items diff --git a/docs/reference/datastructures/7-multi-dicts.md b/docs/reference/datastructures/7-multi-dicts.md new file mode 100644 index 0000000000..22a1083af8 --- /dev/null +++ b/docs/reference/datastructures/7-multi-dicts.md @@ -0,0 +1,13 @@ +# Multi-Dicts + +::: starlite.datastructures.FormMultiDict + options: + members: + - close + - multi_items + +::: starlite.datastructures.QueryMultiDict + options: + members: + - from_query_string + - dict diff --git a/mkdocs.yml b/mkdocs.yml index c0a3170217..d85c7ad077 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -190,7 +190,7 @@ nav: - reference/datastructures/4-background.md - reference/datastructures/5-response-containers.md - reference/datastructures/6-upload-file.md - - reference/datastructures/7-form-multi-dict.md + - reference/datastructures/7-multi-dicts.md - Exceptions: - reference/exceptions/0-base-exceptions.md - reference/exceptions/1-http-exceptions.md diff --git a/starlite/config/cache.py b/starlite/config/cache.py index f2c45121ba..496e342ef9 100644 --- a/starlite/config/cache.py +++ b/starlite/config/cache.py @@ -21,7 +21,7 @@ def default_cache_key_builder(request: "Request[Any, Any]") -> str: Returns: str: combination of url path and query parameters """ - query_params: List[Tuple[str, Any]] = list(request.query_params.items()) + query_params: List[Tuple[str, Any]] = list(request.query_params.dict().items()) query_params.sort(key=lambda x: x[0]) return request.url.path + urlencode(query_params, doseq=True) diff --git a/starlite/connection/base.py b/starlite/connection/base.py index e876d97784..e2c561aed1 100644 --- a/starlite/connection/base.py +++ b/starlite/connection/base.py @@ -1,21 +1,12 @@ -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Generic, - List, - Optional, - TypeVar, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, TypeVar, Union, cast from starlette.datastructures import URL, Address, URLPath from starlite.datastructures.headers import Headers +from starlite.datastructures.multi_dicts import QueryMultiDict from starlite.datastructures.state import State from starlite.exceptions import ImproperlyConfiguredException -from starlite.parsers import parse_cookie_string, parse_query_params +from starlite.parsers import parse_cookie_string from starlite.types.empty import Empty if TYPE_CHECKING: @@ -156,14 +147,14 @@ def headers(self) -> Headers: return cast("Headers", self._headers) @property - def query_params(self) -> Dict[str, List[str]]: + def query_params(self) -> QueryMultiDict: """ Returns: A normalized dict of query parameters. Multiple values for the same key are returned as a list. """ if self._parsed_query is Empty: - self._parsed_query = self.scope["_parsed_query"] = parse_query_params(self.scope.get("query_string", b"")) # type: ignore[typeddict-item] - return cast("Dict[str, List[str]]", self._parsed_query) + self._parsed_query = self.scope["_parsed_query"] = QueryMultiDict.from_query_string(self.scope.get("query_string", b"").decode("utf-8")) # type: ignore[typeddict-item] + return cast("QueryMultiDict", self._parsed_query) @property def path_params(self) -> Dict[str, Any]: diff --git a/starlite/connection/request.py b/starlite/connection/request.py index 0773e1d7cd..72c006f95b 100644 --- a/starlite/connection/request.py +++ b/starlite/connection/request.py @@ -13,7 +13,7 @@ empty_receive, empty_send, ) -from starlite.datastructures.form_multi_dict import FormMultiDict +from starlite.datastructures.multi_dicts import FormMultiDict from starlite.datastructures.upload_file import UploadFile from starlite.enums import RequestEncodingType from starlite.exceptions import InternalServerException diff --git a/starlite/datastructures/__init__.py b/starlite/datastructures/__init__.py index 4036535933..93fc832a30 100644 --- a/starlite/datastructures/__init__.py +++ b/starlite/datastructures/__init__.py @@ -1,12 +1,12 @@ from starlite.datastructures.background_tasks import BackgroundTask, BackgroundTasks from starlite.datastructures.cookie import Cookie -from starlite.datastructures.form_multi_dict import FormMultiDict from starlite.datastructures.headers import ( CacheControlHeader, ETag, Headers, MutableScopeHeaders, ) +from starlite.datastructures.multi_dicts import FormMultiDict from starlite.datastructures.provide import Provide from starlite.datastructures.response_containers import ( File, diff --git a/starlite/datastructures/form_multi_dict.py b/starlite/datastructures/multi_dicts.py similarity index 50% rename from starlite/datastructures/form_multi_dict.py rename to starlite/datastructures/multi_dicts.py index 57d2726d2c..0a709c71ab 100644 --- a/starlite/datastructures/form_multi_dict.py +++ b/starlite/datastructures/multi_dicts.py @@ -1,4 +1,5 @@ -from typing import Any, Iterable, List, Mapping, Optional, Tuple, Union +from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Union +from urllib.parse import parse_qsl from multidict import MultiDict, MultiDictProxy @@ -41,3 +42,41 @@ async def close(self) -> None: for _, value in self.multi_items(): if isinstance(value, UploadFile): await value.close() + + +class QueryMultiDict(MultiDict[Any]): + def __init__( + self, args: Optional[Union["QueryMultiDict", Mapping[str, Any], Iterable[Tuple[str, Any]]]] = None + ) -> None: + super().__init__(MultiDict(args or {})) + + @classmethod + def from_query_string(cls, query_string: str) -> "QueryMultiDict": + """Creates a QueryMultiDict from a query string. + + Args: + query_string: A query string. + + Returns: + A QueryMultiDict instance + """ + _bools = {"true": True, "false": False, "True": True, "False": False} + return cls( + (k, v) if v not in _bools else (k, _bools[v]) for k, v in parse_qsl(query_string, keep_blank_values=True) + ) + + def dict(self) -> Dict[str, List[Any]]: + """ + + Returns: + A dict of lists + """ + out: Dict[str, List[Any]] = {} + + for k, v in self.items(): + if k in out: + out[k].append(v) + else: + out[k] = [v] + + return out diff --git a/starlite/kwargs.py b/starlite/kwargs.py index 9700338389..0295254b67 100644 --- a/starlite/kwargs.py +++ b/starlite/kwargs.py @@ -372,7 +372,9 @@ def to_kwargs(self, connection: Union["WebSocket", "Request"]) -> Dict[str, Any] Returns: A string keyed dictionary of kwargs expected by the handler function and its dependencies. """ - connection_query_params = {k: self._sequence_or_scalar_param(k, v) for k, v in connection.query_params.items()} + connection_query_params = { + k: self._sequence_or_scalar_param(k, v) for k, v in connection.query_params.dict().items() + } path_params = self._collect_params( params=connection.path_params, expected=self.expected_path_params, url=connection.url diff --git a/starlite/parsers.py b/starlite/parsers.py index 1cfd955f49..2a52789d71 100644 --- a/starlite/parsers.py +++ b/starlite/parsers.py @@ -1,8 +1,7 @@ from contextlib import suppress -from functools import reduce from http.cookies import _unquote as unquote_cookie -from typing import TYPE_CHECKING, Any, Dict, List, Tuple -from urllib.parse import parse_qsl, unquote +from typing import TYPE_CHECKING, Any, Dict +from urllib.parse import unquote from orjson import JSONDecodeError, loads from pydantic.fields import SHAPE_LIST, SHAPE_SINGLETON @@ -15,46 +14,12 @@ from pydantic.fields import ModelField - from starlite.datastructures.form_multi_dict import FormMultiDict + from starlite.datastructures.multi_dicts import FormMultiDict _true_values = {"True", "true"} _false_values = {"False", "false"} -def _query_param_reducer(acc: Dict[str, List[Any]], cur: Tuple[str, str]) -> Dict[str, List[str]]: - """ - Reducer function - acc is a dictionary, cur is a tuple of key + value - - We use reduce because python implements reduce in C, which makes it faster than a regular for loop in most cases. - """ - key, value = cur - - if value in _true_values: - value = True # type: ignore - elif value in _false_values: - value = False # type: ignore - - if key in acc: - acc[key].append(value) - else: - acc[key] = [value] - return acc - - -def parse_query_params(query_string: bytes) -> Dict[str, List[str]]: - """Parses and normalize a given connection's query parameters into a - regular dictionary. - - Args: - query_string: A byte-string containing a query - - Returns: - A string keyed dictionary of values. - """ - - return reduce(_query_param_reducer, parse_qsl(query_string.decode("utf-8"), keep_blank_values=True), {}) - - def parse_form_data(media_type: "RequestEncodingType", form_data: "FormMultiDict", field: "ModelField") -> Any: """Transforms the multidict into a regular dict, try to load json on all non-file values. diff --git a/starlite/utils/extractors.py b/starlite/utils/extractors.py index 863509761d..03bcc5d871 100644 --- a/starlite/utils/extractors.py +++ b/starlite/utils/extractors.py @@ -224,7 +224,7 @@ def extract_query(self, connection: "ASGIConnection[Any, Any, Any]") -> Any: Returns: Either a dictionary with the connection's parsed query string or the raw query byte-string. """ - return connection.query_params if self.parse_query else connection.scope.get("query_string", b"") + return connection.query_params.dict() if self.parse_query else connection.scope.get("query_string", b"") @staticmethod def extract_path_params(connection: "ASGIConnection[Any, Any, Any]") -> Dict[str, Any]: diff --git a/tests/connection/request/test_request.py b/tests/connection/request/test_request.py index 776722b259..8115b5ae8c 100644 --- a/tests/connection/request/test_request.py +++ b/tests/connection/request/test_request.py @@ -141,7 +141,7 @@ async def app(scope: "Scope", receive: "Receive", send: "Send") -> None: client = TestClient(app) response = client.get("/?a=123&b=456") - assert response.json() == {"params": {"a": ["123"], "b": ["456"]}} + assert response.json() == {"params": {"a": "123", "b": "456"}} def test_request_headers() -> None: diff --git a/tests/connection/websocket/test_websocket.py b/tests/connection/websocket/test_websocket.py index d36aabce9c..cee9e50783 100644 --- a/tests/connection/websocket/test_websocket.py +++ b/tests/connection/websocket/test_websocket.py @@ -118,7 +118,7 @@ async def app(scope: "Scope", receive: "Receive", send: "Send") -> None: with TestClient(app).websocket_connect("/?a=abc&b=456") as websocket: data = websocket.receive_json() - assert data == {"params": {"a": ["abc"], "b": ["456"]}} + assert data == {"params": {"a": "abc", "b": "456"}} def test_websocket_headers() -> None: diff --git a/tests/datastructures/test_multi_dicts.py b/tests/datastructures/test_multi_dicts.py new file mode 100644 index 0000000000..f13588650b --- /dev/null +++ b/tests/datastructures/test_multi_dicts.py @@ -0,0 +1,28 @@ +from starlite.datastructures.multi_dicts import QueryMultiDict +from starlite.testing import RequestFactory + + +def test_query_multi_dict_parse_query_params() -> None: + query = { + "value": "10", + "veggies": ["tomato", "potato", "aubergine"], + "calories": "122.53", + "healthy": True, + "polluting": False, + } + request = RequestFactory().get(query_params=query) # type: ignore + result = QueryMultiDict.from_query_string(query_string=request.scope.get("query_string", b"").decode("utf-8")) + + assert result.getall("value") == ["10"] + assert result.getall("veggies") == ["tomato", "potato", "aubergine"] + assert result.getall("calories") == ["122.53"] + assert result.getall("healthy") == [True] + assert result.getall("polluting") == [False] + + assert result.dict() == { + "value": ["10"], + "veggies": ["tomato", "potato", "aubergine"], + "calories": ["122.53"], + "healthy": [True], + "polluting": [False], + } diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 47477ec437..2454825c01 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -6,27 +6,7 @@ from starlite import Cookie, RequestEncodingType from starlite.datastructures import FormMultiDict -from starlite.parsers import parse_cookie_string, parse_form_data, parse_query_params -from starlite.testing import RequestFactory - - -def test_parse_query_params() -> None: - query = { - "value": "10", - "veggies": ["tomato", "potato", "aubergine"], - "calories": "122.53", - "healthy": True, - "polluting": False, - } - request = RequestFactory().get(query_params=query) # type: ignore[arg-type] - result = parse_query_params(query_string=request.scope.get("query_string", b"")) - assert result == { - "value": ["10"], - "veggies": ["tomato", "potato", "aubergine"], - "calories": ["122.53"], - "healthy": [True], - "polluting": [False], - } +from starlite.parsers import parse_cookie_string, parse_form_data def test_parse_form_data() -> None: diff --git a/tests/testing/test_testing.py b/tests/testing/test_testing.py index 77885dff83..43be3226ea 100644 --- a/tests/testing/test_testing.py +++ b/tests/testing/test_testing.py @@ -15,6 +15,7 @@ post, ) from starlite.datastructures import Cookie +from starlite.datastructures.multi_dicts import QueryMultiDict from starlite.enums import ParamType from starlite.middleware.session import SessionCookieConfig from starlite.testing import RequestFactory, TestClient @@ -178,7 +179,7 @@ def handler() -> None: assert request.base_url == f"{scheme}://{server}:{port}{root_path}/" assert request.url == f"{scheme}://{server}:{port}{root_path}{path}" assert request.method == HttpMethod.GET - assert request.query_params == {} + assert request.query_params == QueryMultiDict() assert request.user == user assert request.auth == auth assert request.session == session diff --git a/tests/utils/test_extractors.py b/tests/utils/test_extractors.py index 05de3fa7ad..e6a18fd011 100644 --- a/tests/utils/test_extractors.py +++ b/tests/utils/test_extractors.py @@ -30,7 +30,7 @@ async def test_connection_data_extractor() -> None: assert extracted_data.get("path") == request.scope["path"] assert extracted_data.get("path") == request.scope["path"] assert extracted_data.get("path_params") == request.scope["path_params"] - assert extracted_data.get("query") == request.query_params + assert extracted_data.get("query") == request.query_params.dict() assert extracted_data.get("scheme") == request.scope["scheme"] @@ -41,7 +41,7 @@ def test_parse_query() -> None: ) parsed_extracted_data = ConnectionDataExtractor(parse_query=True)(request) unparsed_extracted_data = ConnectionDataExtractor()(request) - assert parsed_extracted_data.get("query") == request.query_params + assert parsed_extracted_data.get("query") == request.query_params.dict() assert unparsed_extracted_data.get("query") == request.scope["query_string"] # Close to avoid warnings about un-awaited coroutines. parsed_extracted_data.get("body").close() # type: ignore From 0c11447c3af109e706e0e98a67288d7aa5402ada Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Mon, 7 Nov 2022 19:41:37 +0100 Subject: [PATCH 2/2] address review comments --- poetry.lock | 80 ++++++++++++++++++++++---- starlite/datastructures/multi_dicts.py | 12 +--- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/poetry.lock b/poetry.lock index 762cea1c26..4cb6e000f2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -268,7 +268,7 @@ test = ["pytest (>=6)"] [[package]] name = "faker" -version = "15.2.0" +version = "15.3.1" description = "Faker is a Python package that generates fake data for you." category = "main" optional = false @@ -350,7 +350,7 @@ tqdm = ["tqdm"] [[package]] name = "greenlet" -version = "2.0.0.post0" +version = "2.0.1" description = "Lightweight in-process concurrent programming" category = "dev" optional = false @@ -358,7 +358,7 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.extras] docs = ["Sphinx", "docutils (<0.18)"] -test = ["faulthandler", "objgraph"] +test = ["faulthandler", "objgraph", "psutil"] [[package]] name = "h11" @@ -645,15 +645,15 @@ dev = ["black", "flaky", "hypothesis", "pre-commit", "pytest", "pytest-cov", "ri [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.5.3" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -1469,8 +1469,8 @@ exceptiongroup = [ {file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"}, ] faker = [ - {file = "Faker-15.2.0-py3-none-any.whl", hash = "sha256:8066d5ef0ca116469292d947593ae4cdf1d97dd83f557dd8a749826cf960020a"}, - {file = "Faker-15.2.0.tar.gz", hash = "sha256:f35b9b47fb84d7334645feba0dd87bbf5aba2b617cd83ec8e1b8c6dcd859a710"}, + {file = "Faker-15.3.1-py3-none-any.whl", hash = "sha256:4a3465624515a6807e8aa7e8eeb85bdd86a2fa53de4e258892dd6be95362462e"}, + {file = "Faker-15.3.1.tar.gz", hash = "sha256:b9dd2fd9a9ac68a4e0c5040cd9e9bfaa099fa8dd15bae5f01f224a45431818d5"}, ] fakeredis = [ {file = "fakeredis-1.10.0-py3-none-any.whl", hash = "sha256:0be420a79fabda234963a2730c4ce609a6d44a598e8dd253ce97785bef944285"}, @@ -1489,7 +1489,63 @@ fsspec = [ {file = "fsspec-2022.10.0.tar.gz", hash = "sha256:cb6092474e90487a51de768170f3afa50ca8982c26150a59072b16433879ff1d"}, ] greenlet = [ - {file = "greenlet-2.0.0.post0.tar.gz", hash = "sha256:ad9abc3e4d2370cecb524421cc5c8a664006aa11d5c1cb3c9250e3bf65ab546e"}, + {file = "greenlet-2.0.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c"}, + {file = "greenlet-2.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515"}, + {file = "greenlet-2.0.1-cp27-cp27m-win32.whl", hash = "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a"}, + {file = "greenlet-2.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524"}, + {file = "greenlet-2.0.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243"}, + {file = "greenlet-2.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45"}, + {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da"}, + {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d"}, + {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd"}, + {file = "greenlet-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617"}, + {file = "greenlet-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a"}, + {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce"}, + {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72"}, + {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82"}, + {file = "greenlet-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd"}, + {file = "greenlet-2.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f"}, + {file = "greenlet-2.0.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f"}, + {file = "greenlet-2.0.1-cp35-cp35m-win32.whl", hash = "sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955"}, + {file = "greenlet-2.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77"}, + {file = "greenlet-2.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148"}, + {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2"}, + {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39"}, + {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92"}, + {file = "greenlet-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928"}, + {file = "greenlet-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd"}, + {file = "greenlet-2.0.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"}, + {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"}, + {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"}, + {file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"}, + {file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"}, + {file = "greenlet-2.0.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"}, + {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"}, + {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"}, + {file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"}, + {file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"}, + {file = "greenlet-2.0.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"}, + {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"}, + {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"}, + {file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"}, + {file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"}, + {file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"}, ] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, @@ -1762,8 +1818,8 @@ picologging = [ {file = "picologging-0.9.0.tar.gz", hash = "sha256:55ac27faa4129a7d272e23e2bb37d76bbf1a38f1b827e18008af1ff347e311fc"}, ] platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, + {file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"}, + {file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, diff --git a/starlite/datastructures/multi_dicts.py b/starlite/datastructures/multi_dicts.py index 0a709c71ab..b511a9b6c7 100644 --- a/starlite/datastructures/multi_dicts.py +++ b/starlite/datastructures/multi_dicts.py @@ -44,7 +44,7 @@ async def close(self) -> None: await value.close() -class QueryMultiDict(MultiDict[Any]): +class QueryMultiDict(MultiDict[Union[str, bool]]): def __init__( self, args: Optional[Union["QueryMultiDict", Mapping[str, Any], Iterable[Tuple[str, Any]]]] = None ) -> None: @@ -71,12 +71,4 @@ def dict(self) -> Dict[str, List[Any]]: Returns: A dict of lists """ - out: Dict[str, List[Any]] = {} - - for k, v in self.items(): - if k in out: - out[k].append(v) - else: - out[k] = [v] - - return out + return {k: self.getall(k) for k in self.keys()}