Refs #29838, Refs #28507 -- Made make_hashable() ignore key order.

This commit is contained in:
Simon Charette 2020-10-05 09:13:14 -04:00 committed by Mariusz Felisiak
parent 981a3426cf
commit 4c675523bd
3 changed files with 10 additions and 5 deletions

View File

@ -196,15 +196,14 @@ class ValidationError(Exception):
return hash(self) == hash(other)
def __hash__(self):
# Ignore params and messages ordering.
if hasattr(self, 'message'):
return hash((
self.message,
self.code,
tuple(sorted(make_hashable(self.params))) if self.params else None,
make_hashable(self.params),
))
if hasattr(self, 'error_dict'):
return hash(tuple(sorted(make_hashable(self.error_dict))))
return hash(make_hashable(self.error_dict))
return hash(tuple(sorted(self.error_list, key=operator.attrgetter('message'))))

View File

@ -2,10 +2,15 @@ from django.utils.itercompat import is_iterable
def make_hashable(value):
"""
Attempt to make value hashable or raise a TypeError if it fails.
The returned value should generate the same hash for equal values.
"""
if isinstance(value, dict):
return tuple([
(key, make_hashable(nested_value))
for key, nested_value in value.items()
for key, nested_value in sorted(value.items())
])
# Try hash to avoid converting a hashable iterable (e.g. string, frozenset)
# to a tuple.

View File

@ -10,7 +10,8 @@ class TestHashable(SimpleTestCase):
({}, ()),
({'a'}, ('a',)),
(frozenset({'a'}), {'a'}),
({'a': 1}, (('a', 1),)),
({'a': 1, 'b': 2}, (('a', 1), ('b', 2))),
({'b': 2, 'a': 1}, (('a', 1), ('b', 2))),
(('a', ['b', 1]), ('a', ('b', 1))),
(('a', {'b': 1}), ('a', (('b', 1),))),
)