From 2db60f8ddf5d6c0d25d7a778b11d403ddee1408a Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 5 Jun 2020 10:52:50 -0700 Subject: [PATCH] Rename RequestParameters to MultiParams, refs #799 --- datasette/utils/__init__.py | 25 +++++++++++++++++-------- datasette/utils/asgi.py | 4 ++-- datasette/views/table.py | 4 ++-- docs/internals.rst | 10 ++++++---- tests/test_utils.py | 9 +++++++++ 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 2eb315024d..083fba0c17 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -753,15 +753,24 @@ def escape_fts(query): ) -class RequestParameters: +class MultiParams: def __init__(self, data): - # data is a dictionary of key => [list, of, values] - assert isinstance(data, dict), "data should be a dictionary of key => [list]" - for key in data: - assert isinstance( - data[key], list - ), "data should be a dictionary of key => [list]" - self._data = data + # data is a dictionary of key => [list, of, values] or a list of [["key", "value"]] pairs + if isinstance(data, dict): + for key in data: + assert isinstance( + data[key], list + ), "dictionary data should be a dictionary of key => [list]" + self._data = data + elif isinstance(data, list): + new_data = {} + for item in data: + assert ( + isinstance(item, list) and len(item) == 2 + ), "list data should be a list of [key, value] pairs" + key, value = item + new_data.setdefault(key, []).append(value) + self._data = new_data def __contains__(self, key): return key in self._data diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index c7810a5006..ba131dc8be 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -1,5 +1,5 @@ import json -from datasette.utils import RequestParameters +from datasette.utils import MultiParams from mimetypes import guess_type from urllib.parse import parse_qs, urlunparse, parse_qsl from pathlib import Path @@ -68,7 +68,7 @@ def query_string(self): @property def args(self): - return RequestParameters(parse_qs(qs=self.query_string)) + return MultiParams(parse_qs(qs=self.query_string)) async def post_vars(self): body = [] diff --git a/datasette/views/table.py b/datasette/views/table.py index 79bf8b0852..ec1b6c7c24 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -8,7 +8,7 @@ from datasette.database import QueryInterrupted from datasette.utils import ( CustomRow, - RequestParameters, + MultiParams, append_querystring, compound_keys_after_sql, escape_sqlite, @@ -286,7 +286,7 @@ async def data( order_by = "" # Ensure we don't drop anything with an empty value e.g. ?name__exact= - args = RequestParameters( + args = MultiParams( urllib.parse.parse_qs(request.query_string, keep_blank_values=True) ) diff --git a/docs/internals.rst b/docs/internals.rst index 4d51d6148f..4b4adc5ea5 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -39,7 +39,7 @@ The request object is passed to various plugin hooks. It represents an incoming ``.query_string`` - string The querystring component of the request, without the ``?`` - e.g. ``name__contains=sam&age__gt=10``. -``.args`` - RequestParameters +``.args`` - MultiParams An object representing the parsed querystring parameters, see below. The object also has one awaitable method: @@ -47,10 +47,12 @@ The object also has one awaitable method: ``await request.post_vars()`` - dictionary Returns a dictionary of form variables that were submitted in the request body via ``POST``. -The RequestParameters class ---------------------------- +.. _internals_multiparams: -``request.args`` is a ``RequestParameters`` object - a dictionary-like object which provides access to querystring parameters that may have multiple values. +The MultiParams class +--------------------- + +``request.args`` is a ``MultiParams`` object - a dictionary-like object which provides access to querystring parameters that may have multiple values. Consider the querystring ``?foo=1&foo=2&bar=3`` - with two values for ``foo`` and one value for ``bar``. diff --git a/tests/test_utils.py b/tests/test_utils.py index 01a1046866..ffe14587bb 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -437,3 +437,12 @@ def foo(a, b): with pytest.raises(TypeError): utils.call_with_supported_arguments(foo, a=1) + + +def test_multi_params_list(): + p1 = utils.MultiParams([["foo", "bar"], ["foo", "baz"]]) + assert "bar" == p1["foo"] + assert ["bar", "baz"] == p1.getlist("foo") + # Should raise an error if list isn't pairs + with pytest.raises(AssertionError): + utils.MultiParams([["foo", "bar"], ["foo", "baz", "bar"]])