From e75a5bade4589762e5d9820f99a1bf84509c667a Mon Sep 17 00:00:00 2001 From: kokotatan Date: Sat, 3 Oct 2026 22:40:28 +0900 Subject: [PATCH] Fix mixed comparison key errors in min_by and max_by --- CHANGELOG.rst | 3 ++- jmespath/functions.py | 3 +++ tests/compliance/functions.json | 8 ++++++++ tests/test_functions.py | 27 +++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc673df4..36bcbfbc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,8 @@ Next Release (TBD) ================== -* No changes yet. +* Raise ``JMESPathTypeError`` for mixed number and string comparison keys in + ``min_by`` and ``max_by``. 1.1.0 diff --git a/jmespath/functions.py b/jmespath/functions.py index 627b569d..e70a4299 100644 --- a/jmespath/functions.py +++ b/jmespath/functions.py @@ -348,6 +348,7 @@ def _func_max_by(self, array, expref): def _create_key_func(self, expref, allowed_types, function_name): def keyfunc(x): + nonlocal allowed_types result = expref.visit(expref.expression, x) actual_typename = type(result).__name__ jmespath_type = self._convert_to_jmespath_type(actual_typename) @@ -355,6 +356,8 @@ def keyfunc(x): if jmespath_type not in allowed_types: raise exceptions.JMESPathTypeError( function_name, result, jmespath_type, allowed_types) + # All comparison keys must have the same JMESPath type. + allowed_types = [jmespath_type] return result return keyfunc diff --git a/tests/compliance/functions.json b/tests/compliance/functions.json index 7b554450..bca4041b 100644 --- a/tests/compliance/functions.json +++ b/tests/compliance/functions.json @@ -693,6 +693,10 @@ "expression": "max_by(people, &bool)", "error": "invalid-type" }, + { + "expression": "max_by(people, &name)", + "error": "invalid-type" + }, { "expression": "max_by(people, &extra)", "error": "invalid-type" @@ -717,6 +721,10 @@ "expression": "min_by(people, &bool)", "error": "invalid-type" }, + { + "expression": "min_by(people, &name)", + "error": "invalid-type" + }, { "expression": "min_by(people, &extra)", "error": "invalid-type" diff --git a/tests/test_functions.py b/tests/test_functions.py index a352d69e..b9f1c6e6 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -9,6 +9,33 @@ class TestFunctions(unittest.TestCase): + def test_extrema_reject_mixed_key_types(self): + for function in ('min_by', 'max_by'): + for keys, expected_type, actual_type in ( + ([1, '2'], 'number', 'string'), + (['1', 2], 'string', 'number')): + with self.subTest(function=function, keys=keys): + data = [{'key': key} for key in keys] + with self.assertRaises(exceptions.JMESPathTypeError) as e: + jmespath.search('%s(@, &key)' % function, data) + self.assertEqual(e.exception.function_name, function) + self.assertEqual(e.exception.current_value, keys[1]) + self.assertEqual(e.exception.actual_type, actual_type) + self.assertEqual(e.exception.expected_types, [expected_type]) + + def test_extrema_accept_integer_and_float_keys(self): + data = [{'key': 2}, {'key': 1.5}, {'key': 3.5}] + self.assertEqual(jmespath.search('min_by(@, &key)', data), data[1]) + self.assertEqual(jmespath.search('max_by(@, &key)', data), data[2]) + + def test_extrema_key_type_is_local_to_each_search(self): + for function in ('min_by', 'max_by'): + with self.subTest(function=function): + expression = jmespath.compile('%s(@, &key)' % function) + for key in (1, 'a'): + data = [{'key': key}] + self.assertEqual(expression.search(data), data[0]) + def test_can_max_datetimes(self): # This is python specific behavior, but JMESPath does not specify what # you should do with language specific types. We're going to add the