Selaa lähdekoodia

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.
aspalding 6 vuotta sitten
vanhempi
commit
217f82d713
2 muutettua tiedostoa jossa 23 lisäystä ja 3 poistoa
  1. 12 2
      django/utils/hashable.py
  2. 11 1
      tests/utils_tests/test_hashable.py

+ 12 - 2
django/utils/hashable.py

@@ -1,9 +1,19 @@
+from django.utils.itercompat import is_iterable
+
+
 def make_hashable(value):
-    if isinstance(value, list):
-        return tuple(map(make_hashable, value))
     if isinstance(value, dict):
         return tuple([
             (key, make_hashable(nested_value))
             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

+ 11 - 1
tests/utils_tests/test_hashable.py

@@ -8,9 +8,11 @@ class TestHashable(SimpleTestCase):
             ([], ()),
             (['a', 1], ('a', 1)),
             ({}, ()),
-            ({'a'}, {'a'}),
+            ({'a'}, ('a',)),
             (frozenset({'a'}), {'a'}),
             ({'a': 1}, (('a', 1),)),
+            (('a', ['b', 1]), ('a', ('b', 1))),
+            (('a', {'b': 1}), ('a', (('b', 1),))),
         )
         for value, expected in tests:
             with self.subTest(value=value):
@@ -19,7 +21,15 @@ class TestHashable(SimpleTestCase):
     def test_count_equal(self):
         tests = (
             ({'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:
             with self.subTest(value=value):
                 self.assertCountEqual(make_hashable(value), expected)
+
+    def test_unhashable(self):
+        class Unhashable:
+            __hash__ = None
+
+        with self.assertRaisesMessage(TypeError, "unhashable type: 'Unhashable'"):
+            make_hashable(Unhashable())