Skip to content

Commit

Permalink
Updated MultiValueDict.update() to mirror dict.update() behavior.
Browse files Browse the repository at this point in the history
Changes in behavior include:

- Accepting iteration over empty sequences, updating nothing.
- Accepting iterable of 2-tuples providing key-value pairs.
- Failing with the same or comparable exceptions for invalid input.

Notably this replaces the previous attempt to catch TypeError which was
unreachable as the call to .items() resulted in AttributeError on
non-dict objects.
  • Loading branch information
ngnpope committed Oct 5, 2020
1 parent b55d817 commit 40c3896
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 8 deletions.
15 changes: 7 additions & 8 deletions django/utils/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,15 @@ def update(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("update expected at most 1 argument, got %d" % len(args))
if args:
other_dict = args[0]
if isinstance(other_dict, MultiValueDict):
for key, value_list in other_dict.lists():
arg = args[0]
if isinstance(arg, MultiValueDict):
for key, value_list in arg.lists():
self.setlistdefault(key).extend(value_list)
else:
try:
for key, value in other_dict.items():
self.setlistdefault(key).append(value)
except TypeError:
raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary")
if isinstance(arg, Mapping):
arg = arg.items()
for key, value in arg:
self.setlistdefault(key).append(value)
for key, value in kwargs.items():
self.setlistdefault(key).append(value)

Expand Down
36 changes: 36 additions & 0 deletions tests/utils_tests/test_datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CaseInsensitiveMapping, DictWrapper, ImmutableList, MultiValueDict,
MultiValueDictKeyError, OrderedSet,
)
from django.utils.version import PY37


class OrderedSetTests(SimpleTestCase):
Expand Down Expand Up @@ -204,6 +205,41 @@ def test_update_kwargs(self):
x.update(a=4, b=5)
self.assertEqual(list(x.lists()), [('a', [1, 4]), ('b', [2, 5]), ('c', [3])])

def test_update_with_empty_iterable(self):
for value in ['', b'', (), [], set(), {}]:
d = MultiValueDict()
d.update(value)
self.assertEqual(d, MultiValueDict())

def test_update_with_iterable_of_pairs(self):
for value in [(('a', 1),), [('a', 1)], {('a', 1)}]:
d = MultiValueDict()
d.update(value)
self.assertEqual(d, MultiValueDict({'a': [1]}))

def test_update_with_non_iterable_raises_typeerror(self):
for value in [None, True, False, 123, 123.45]:
type_ = type(value)
msg = "'%s' object is not iterable" % type_.__name__
with self.subTest(type_), self.assertRaisesMessage(TypeError, msg):
MultiValueDict().update(value)

def test_update_with_unspupported_non_empty_iterable_raises(self):
# TypeError: cannot convert dictionary update sequence element #0 to a sequence
for value in [b'123', b'abc', (1, 2, 3), [1, 2, 3], {1, 2, 3}]:
if PY37:
msg = 'cannot unpack non-iterable int object'
else:
msg = "'int' object is not iterable"
with self.subTest(value), self.assertRaisesMessage(TypeError, msg):
MultiValueDict().update(value)

# ValueError: dictionary update sequence element #0 has length 1; 2 is required
for value in ['123', 'abc', ('a', 'b', 'c'), ['a', 'b', 'c'], {'a', 'b', 'c'}]:
msg = 'not enough values to unpack (expected 2, got 1)'
with self.subTest(value), self.assertRaisesMessage(ValueError, msg):
MultiValueDict().update(value)


class ImmutableListTests(SimpleTestCase):

Expand Down

0 comments on commit 40c3896

Please sign in to comment.