From d2c0697e2574fce910f7107f95ecbac7456e5f05 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Wed, 24 Jun 2026 23:52:48 +0200 Subject: [PATCH] fix: freeze unhashable args in Query.test to prevent TypeError Passing list or dict values as extra arguments to Query().field.test() raised TypeError: unhashable type when the resulting QueryInstance was hashed (e.g. on first use with a caching Table). The other query methods (any, all, one_of, fragment) already call freeze() on their arguments; apply the same treatment to each element of the *args tuple in Query.test(). Fixes #517 --- tests/test_queries.py | 31 +++++++++++++++++++++++++++++++ tinydb/queries.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/test_queries.py b/tests/test_queries.py index 6e0dd8faa..7ddfa2bb7 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -222,6 +222,37 @@ def test(value, minimum, maximum): assert hash(query) +def test_custom_with_unhashable_params(): + """Test that Query.test works when extra args contain lists or dicts.""" + + def in_allowed(value, allowed): + return value in allowed + + # list argument must not raise TypeError when hashing the query + query = Query().val.test(in_allowed, [1, 2, 3]) + assert query({'val': 1}) + assert not query({'val': 4}) + assert hash(query) + + # dict argument must not raise TypeError when hashing the query + def has_key(value, mapping): + return value in mapping + + query2 = Query().val.test(has_key, {'a': 1, 'b': 2}) + assert query2({'val': 'a'}) + assert not query2({'val': 'c'}) + assert hash(query2) + + # identical args produce the same hash (cache key correctness) + q_a = Query().val.test(in_allowed, [1, 2, 3]) + q_b = Query().val.test(in_allowed, [1, 2, 3]) + assert hash(q_a) == hash(q_b) + + # different args produce different hashes + q_c = Query().val.test(in_allowed, [1, 2, 4]) + assert hash(q_a) != hash(q_c) + + def test_any(): query = Query().followers.any(Query().name == 'don') diff --git a/tinydb/queries.py b/tinydb/queries.py index 66810ce77..a9320fabf 100644 --- a/tinydb/queries.py +++ b/tinydb/queries.py @@ -388,7 +388,7 @@ def test(self, func: Callable[[Mapping], bool], *args) -> QueryInstance: """ return self._generate_test( lambda value: func(value, *args), - ('test', self._path, func, args) + ('test', self._path, func, tuple(freeze(arg) for arg in args)) ) def any(self, cond: Union[QueryInstance, list[Any]]) -> QueryInstance: