Skip to content

Commit

Permalink
New implementation for RequestParams
Browse files Browse the repository at this point in the history
- no longer subclasses dict
- request.args[key] now returns first item, not all items
- removed request.raw_args entirely

Closes #774
  • Loading branch information
simonw committed May 29, 2020
1 parent f272cbc commit 81be313
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 16 deletions.
2 changes: 1 addition & 1 deletion datasette/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 27 additions & 3 deletions datasette/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 0 additions & 5 deletions datasette/utils/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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""
Expand Down
6 changes: 3 additions & 3 deletions datasette/views/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions docs/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
10 changes: 10 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down

0 comments on commit 81be313

Please sign in to comment.