diff --git a/datasette/renderer.py b/datasette/renderer.py index 349c29223c..3f921fe77d 100644 --- a/datasette/renderer.py +++ b/datasette/renderer.py @@ -32,7 +32,7 @@ def json_renderer(args, data, view_name): # Handle the _json= parameter which may modify data["rows"] json_cols = [] if "_json" in args: - json_cols = args["_json"] + json_cols = args.getlist("_json") if json_cols and "rows" in data and "columns" in data: data["rows"] = convert_specific_columns_to_json( data["rows"], data["columns"], json_cols diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 9b4f21ba28..bf96541399 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -753,17 +753,41 @@ def escape_fts(query): ) -class RequestParameters(dict): +class RequestParameters: + 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 + + def __contains__(self, key): + return key in self._data + + def __getitem__(self, key): + return self._data[key][0] + + def keys(self): + return self._data.keys() + + def __iter__(self): + yield from self._data.keys() + + def __len__(self): + return len(self._data) + def get(self, name, default=None): "Return first value in the list, if available" try: - return super().get(name)[0] + return self._data.get(name)[0] except (KeyError, TypeError): return default def getlist(self, name): "Return full list" - return super().get(name) or [] + return self._data.get(name) or [] class ConnectionProblem(Exception): diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 62a2a0c83b..24398b7724 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -63,11 +63,6 @@ def query_string(self): def args(self): return RequestParameters(parse_qs(qs=self.query_string)) - @property - def raw_args(self): - # Deprecated, undocumented - may be removed in Datasette 1.0 - return {key: value[0] for key, value in self.args.items()} - async def post_vars(self): body = [] body = b"" diff --git a/datasette/views/table.py b/datasette/views/table.py index d1d92bb1d9..a629346fdf 100644 --- a/datasette/views/table.py +++ b/datasette/views/table.py @@ -277,11 +277,11 @@ async def data( # it can still be queried using ?_col__exact=blah special_args = {} other_args = [] - for key, value in args.items(): + for key in args: if key.startswith("_") and "__" not in key: - special_args[key] = value[0] + special_args[key] = args[key] else: - for v in value: + for v in args.getlist(key): other_args.append((key, v)) # Handle ?_filter_column and redirect, if present diff --git a/docs/internals.rst b/docs/internals.rst index bbf10caedf..ea015dbc27 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -268,12 +268,16 @@ The object also has one awaitable method: The RequestParameters class --------------------------- -This class, returned by ``request.args``, is a subclass of a Python dictionary that provides methods for working with keys that map to lists of values. +This class, returned by ``request.args``, is a dictionary-like object. -Conider the querystring ``?foo=1&foo=2``. This will produce a ``request.args`` that looks like this:: +Consider the querystring ``?foo=1&foo=2``. This will produce a ``request.args`` that looks like this:: RequestParameters({"foo": ["1", "2"]}) -Calling ``request.args.get("foo")`` will return the first value, ``"1"``. If that key is not present it will return ``None`` - or the second argument if you passed one, which will be used as the default. +``request.args["foo"]`` returns the first value, ``"1"`` - or raises ``KeyError`` if that key is missing. -Calling ``request.args.getlist("foo")`` will return the full list, ``["1", "2"]``. If you call it on a missing key it will return ``[]``. +``request.args.get("foo")`` returns ``"1"`` - or ``None`` if the key is missing. A second argument can be used to specify a different default value. + +``request.args.getlist("foo")`` returns the full list, ``["1", "2"]``. If you call it on a missing key it will return ``[]``. + +You can use ``if key in request.args`` to check if a key is present. ``for key in request.args`` will iterate through the keys, or you can use ``request.args.keys()`` to get all of the keys. diff --git a/tests/test_utils.py b/tests/test_utils.py index ffb66ca58a..9d6f45b0cb 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -452,8 +452,18 @@ def test_request_args(): request = Request.fake("/foo?multi=1&multi=2&single=3") assert "1" == request.args.get("multi") assert "3" == request.args.get("single") + assert "1" == request.args["multi"] + assert "3" == request.args["single"] assert ["1", "2"] == request.args.getlist("multi") assert [] == request.args.getlist("missing") + assert "multi" in request.args + assert "single" in request.args + assert "missing" not in request.args + expected = ["multi", "single"] + assert expected == list(request.args.keys()) + for i, key in enumerate(request.args): + assert expected[i] == key + assert 2 == len(request.args) with pytest.raises(KeyError): request.args["missing"]