From 834c4ec8e4cfc43acf0525e0a4d8c914534f6afd Mon Sep 17 00:00:00 2001 From: aspalding Date: Tue, 16 Oct 2018 10:01:31 -0500 Subject: [PATCH] Moved make_hashable() to django.utils and added tests. --- django/db/models/expressions.py | 11 +---------- django/utils/hashable.py | 9 +++++++++ tests/utils_tests/test_hashable.py | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 django/utils/hashable.py create mode 100644 tests/utils_tests/test_hashable.py diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 2bf6316c2e..b21a3b7526 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -9,6 +9,7 @@ from django.db.models import fields from django.db.models.query_utils import Q from django.utils.deconstruct import deconstructible from django.utils.functional import cached_property +from django.utils.hashable import make_hashable class SQLiteNumericMixin: @@ -138,16 +139,6 @@ class Combinable: ) -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() - ]) - return value - - @deconstructible class BaseExpression: """Base class for all query expressions.""" diff --git a/django/utils/hashable.py b/django/utils/hashable.py new file mode 100644 index 0000000000..859ea8073e --- /dev/null +++ b/django/utils/hashable.py @@ -0,0 +1,9 @@ +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() + ]) + return value diff --git a/tests/utils_tests/test_hashable.py b/tests/utils_tests/test_hashable.py new file mode 100644 index 0000000000..2310c4c605 --- /dev/null +++ b/tests/utils_tests/test_hashable.py @@ -0,0 +1,25 @@ +from django.test import SimpleTestCase +from django.utils.hashable import make_hashable + + +class TestHashable(SimpleTestCase): + def test_equal(self): + tests = ( + ([], ()), + (['a', 1], ('a', 1)), + ({}, ()), + ({'a'}, {'a'}), + (frozenset({'a'}), {'a'}), + ({'a': 1}, (('a', 1),)), + ) + for value, expected in tests: + with self.subTest(value=value): + self.assertEqual(make_hashable(value), expected) + + def test_count_equal(self): + tests = ( + ({'a': 1, 'b': ['a', 1]}, (('a', 1), ('b', ('a', 1)))), + ) + for value, expected in tests: + with self.subTest(value=value): + self.assertCountEqual(make_hashable(value), expected)