Refs #29838 -- Fixed make_hashable() for values that have lists or dicts nested in tuples.
And for non-hashable values that are iterable, e.g. sets.
This commit is contained in:
parent
834c4ec8e4
commit
217f82d713
|
@ -1,9 +1,19 @@
|
||||||
|
from django.utils.itercompat import is_iterable
|
||||||
|
|
||||||
|
|
||||||
def make_hashable(value):
|
def make_hashable(value):
|
||||||
if isinstance(value, list):
|
|
||||||
return tuple(map(make_hashable, value))
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return tuple([
|
return tuple([
|
||||||
(key, make_hashable(nested_value))
|
(key, make_hashable(nested_value))
|
||||||
for key, nested_value in value.items()
|
for key, nested_value in value.items()
|
||||||
])
|
])
|
||||||
|
# Try hash to avoid converting a hashable iterable (e.g. string, frozenset)
|
||||||
|
# to a tuple.
|
||||||
|
try:
|
||||||
|
hash(value)
|
||||||
|
except TypeError:
|
||||||
|
if is_iterable(value):
|
||||||
|
return tuple(map(make_hashable, value))
|
||||||
|
# Non-hashable, non-iterable.
|
||||||
|
raise
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -8,9 +8,11 @@ class TestHashable(SimpleTestCase):
|
||||||
([], ()),
|
([], ()),
|
||||||
(['a', 1], ('a', 1)),
|
(['a', 1], ('a', 1)),
|
||||||
({}, ()),
|
({}, ()),
|
||||||
({'a'}, {'a'}),
|
({'a'}, ('a',)),
|
||||||
(frozenset({'a'}), {'a'}),
|
(frozenset({'a'}), {'a'}),
|
||||||
({'a': 1}, (('a', 1),)),
|
({'a': 1}, (('a', 1),)),
|
||||||
|
(('a', ['b', 1]), ('a', ('b', 1))),
|
||||||
|
(('a', {'b': 1}), ('a', (('b', 1),))),
|
||||||
)
|
)
|
||||||
for value, expected in tests:
|
for value, expected in tests:
|
||||||
with self.subTest(value=value):
|
with self.subTest(value=value):
|
||||||
|
@ -19,7 +21,15 @@ class TestHashable(SimpleTestCase):
|
||||||
def test_count_equal(self):
|
def test_count_equal(self):
|
||||||
tests = (
|
tests = (
|
||||||
({'a': 1, 'b': ['a', 1]}, (('a', 1), ('b', ('a', 1)))),
|
({'a': 1, 'b': ['a', 1]}, (('a', 1), ('b', ('a', 1)))),
|
||||||
|
({'a': 1, 'b': ('a', [1, 2])}, (('a', 1), ('b', ('a', (1, 2))))),
|
||||||
)
|
)
|
||||||
for value, expected in tests:
|
for value, expected in tests:
|
||||||
with self.subTest(value=value):
|
with self.subTest(value=value):
|
||||||
self.assertCountEqual(make_hashable(value), expected)
|
self.assertCountEqual(make_hashable(value), expected)
|
||||||
|
|
||||||
|
def test_unhashable(self):
|
||||||
|
class Unhashable:
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(TypeError, "unhashable type: 'Unhashable'"):
|
||||||
|
make_hashable(Unhashable())
|
||||||
|
|
Loading…
Reference in New Issue