Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions jmespath/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,16 @@ 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)
# allowed_types is in term of jmespath types, not python types.
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

Expand Down
8 changes: 8 additions & 0 deletions tests/compliance/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
27 changes: 27 additions & 0 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down