summaryrefslogtreecommitdiffstats
path: root/contrib/python/jmespath
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-02-06 22:34:50 +0300
committerrobot-piglet <[email protected]>2026-02-06 22:52:06 +0300
commitd584f96e49dbfe28c5293ac7c830dceaf886484e (patch)
tree01a491a5829e57d4c8e8a168f62ff756e18fbcbe /contrib/python/jmespath
parent20815db1eef8a55d50d396413f6e6ceb24c7c911 (diff)
Intermediate changes
commit_hash:b5d76ab428f75253e9c745952720fbcb00a960c8
Diffstat (limited to 'contrib/python/jmespath')
-rw-r--r--contrib/python/jmespath/py3/.dist-info/METADATA13
-rw-r--r--contrib/python/jmespath/py3/LICENSE21
-rw-r--r--contrib/python/jmespath/py3/LICENSE.txt20
-rw-r--r--contrib/python/jmespath/py3/jmespath/__init__.py2
-rw-r--r--contrib/python/jmespath/py3/jmespath/functions.py2
-rw-r--r--contrib/python/jmespath/py3/jmespath/parser.py31
-rw-r--r--contrib/python/jmespath/py3/tests/test_compliance.py27
-rw-r--r--contrib/python/jmespath/py3/tests/test_custom_functions.py25
-rw-r--r--contrib/python/jmespath/py3/tests/test_functions.py57
-rw-r--r--contrib/python/jmespath/py3/tests/test_lexer.py160
-rw-r--r--contrib/python/jmespath/py3/tests/test_search.py64
-rw-r--r--contrib/python/jmespath/py3/ya.make2
12 files changed, 369 insertions, 55 deletions
diff --git a/contrib/python/jmespath/py3/.dist-info/METADATA b/contrib/python/jmespath/py3/.dist-info/METADATA
index 00fb771cc17..465f77aad5e 100644
--- a/contrib/python/jmespath/py3/.dist-info/METADATA
+++ b/contrib/python/jmespath/py3/.dist-info/METADATA
@@ -1,26 +1,27 @@
Metadata-Version: 2.1
Name: jmespath
-Version: 1.0.1
+Version: 1.1.0
Summary: JSON Matching Expressions
Home-page: https://github.com/jmespath/jmespath.py
Author: James Saryerwinnie
Author-email: [email protected]
License: MIT
-Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=3.7
+Requires-Python: >=3.9
+License-File: LICENSE
JMESPath
========
@@ -236,5 +237,3 @@ Discuss
Join us on our `Gitter channel <https://gitter.im/jmespath/chat>`__
if you want to chat or if you have any questions.
-
-
diff --git a/contrib/python/jmespath/py3/LICENSE b/contrib/python/jmespath/py3/LICENSE
new file mode 100644
index 00000000000..9c520c6bbff
--- /dev/null
+++ b/contrib/python/jmespath/py3/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/contrib/python/jmespath/py3/LICENSE.txt b/contrib/python/jmespath/py3/LICENSE.txt
deleted file mode 100644
index aa689285366..00000000000
--- a/contrib/python/jmespath/py3/LICENSE.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish, dis-
-tribute, sublicense, and/or sell copies of the Software, and to permit
-persons to whom the Software is furnished to do so, subject to the fol-
-lowing conditions:
-
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
-ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
diff --git a/contrib/python/jmespath/py3/jmespath/__init__.py b/contrib/python/jmespath/py3/jmespath/__init__.py
index c2439e37d47..baf73827e77 100644
--- a/contrib/python/jmespath/py3/jmespath/__init__.py
+++ b/contrib/python/jmespath/py3/jmespath/__init__.py
@@ -1,7 +1,7 @@
from jmespath import parser
from jmespath.visitor import Options
-__version__ = '1.0.1'
+__version__ = '1.1.0'
def compile(expression):
diff --git a/contrib/python/jmespath/py3/jmespath/functions.py b/contrib/python/jmespath/py3/jmespath/functions.py
index 11ab56aca2e..627b569d821 100644
--- a/contrib/python/jmespath/py3/jmespath/functions.py
+++ b/contrib/python/jmespath/py3/jmespath/functions.py
@@ -168,7 +168,7 @@ class Functions(metaclass=FunctionRegistry):
@signature({'types': ['array-number']})
def _func_avg(self, arg):
if arg:
- return sum(arg) / float(len(arg))
+ return sum(arg) / len(arg)
else:
return None
diff --git a/contrib/python/jmespath/py3/jmespath/parser.py b/contrib/python/jmespath/py3/jmespath/parser.py
index 4706688040a..cc8e804e606 100644
--- a/contrib/python/jmespath/py3/jmespath/parser.py
+++ b/contrib/python/jmespath/py3/jmespath/parser.py
@@ -25,8 +25,6 @@ A few notes on the implementation.
consuming from the token iterator one token at a time.
"""
-import random
-
from jmespath import lexer
from jmespath.compat import with_repr_method
from jmespath import ast
@@ -73,7 +71,7 @@ class Parser(object):
# The _MAX_SIZE most recent expressions are cached in
# _CACHE dict.
_CACHE = {}
- _MAX_SIZE = 128
+ _MAX_SIZE = 512
def __init__(self, lookahead=2):
self.tokenizer = None
@@ -82,13 +80,26 @@ class Parser(object):
self._index = 0
def parse(self, expression):
- cached = self._CACHE.get(expression)
- if cached is not None:
- return cached
+ try:
+ return self._CACHE[expression]
+ except KeyError:
+ pass
parsed_result = self._do_parse(expression)
+ if len(self._CACHE) >= self._MAX_SIZE:
+ try:
+ del self._CACHE[next(iter(self._CACHE))]
+ except (KeyError, StopIteration, RuntimeError):
+ # KeyError - Another thread else already deleted the key.
+ # RuntimeError - Another modified the cache.
+ # StopIteration - (Unlikely) Cache is empty.
+ #
+ # If we encounter an error we should NOT be adding to the
+ # cache. To ensure we do not exceed self._MAX_SIZE, we
+ # can only add to the cache if we successfully removed
+ # an element from the cache, otherwise this can grow
+ # unbounded.
+ return parsed_result
self._CACHE[expression] = parsed_result
- if len(self._CACHE) > self._MAX_SIZE:
- self._free_cache_entries()
return parsed_result
def _do_parse(self, expression):
@@ -488,10 +499,6 @@ class Parser(object):
raise exceptions.ParseError(
lex_position, actual_value, actual_type, message)
- def _free_cache_entries(self):
- for key in random.sample(list(self._CACHE.keys()), int(self._MAX_SIZE / 2)):
- self._CACHE.pop(key, None)
-
@classmethod
def purge(cls):
"""Clear the expression compilation cache."""
diff --git a/contrib/python/jmespath/py3/tests/test_compliance.py b/contrib/python/jmespath/py3/tests/test_compliance.py
index cff40b014c9..c35b8f9c15f 100644
--- a/contrib/python/jmespath/py3/tests/test_compliance.py
+++ b/contrib/python/jmespath/py3/tests/test_compliance.py
@@ -48,19 +48,20 @@ def _walk_files():
def load_cases(full_path):
- all_test_data = json.load(open(full_path), object_pairs_hook=OrderedDict)
- for test_data in all_test_data:
- given = test_data['given']
- for case in test_data['cases']:
- if 'result' in case:
- test_type = 'result'
- elif 'error' in case:
- test_type = 'error'
- elif 'bench' in case:
- test_type = 'bench'
- else:
- raise RuntimeError("Unknown test type: %s" % json.dumps(case))
- yield (given, test_type, case)
+ with open(full_path, 'r', encoding='utf-8') as f:
+ all_test_data = json.load(f, object_pairs_hook=OrderedDict)
+ for test_data in all_test_data:
+ given = test_data['given']
+ for case in test_data['cases']:
+ if 'result' in case:
+ test_type = 'result'
+ elif 'error' in case:
+ test_type = 'error'
+ elif 'bench' in case:
+ test_type = 'bench'
+ else:
+ raise RuntimeError(f"Unknown test type: {json.dumps(case)}")
+ yield (given, test_type, case)
@pytest.mark.parametrize(
diff --git a/contrib/python/jmespath/py3/tests/test_custom_functions.py b/contrib/python/jmespath/py3/tests/test_custom_functions.py
new file mode 100644
index 00000000000..9932908b693
--- /dev/null
+++ b/contrib/python/jmespath/py3/tests/test_custom_functions.py
@@ -0,0 +1,25 @@
+import unittest
+
+import jmespath
+from jmespath import functions
+
+
+class CustomFunctions(functions.Functions):
+ @functions.signature({'types': ['string', 'array', 'object', 'null']})
+ def _func_length0(self, s):
+ return 0 if s is None else len(s)
+
+
+class TestCustomFunctions(unittest.TestCase):
+ def setUp(self):
+ self.options = jmespath.Options(custom_functions=CustomFunctions())
+
+ def test_null_to_nonetype(self):
+ data = {
+ 'a': {
+ 'b': [1, 2, 3]
+ }
+ }
+
+ self.assertEqual(jmespath.search('length0(a.b)', data, self.options), 3)
+ self.assertEqual(jmespath.search('length0(a.c)', data, self.options), 0)
diff --git a/contrib/python/jmespath/py3/tests/test_functions.py b/contrib/python/jmespath/py3/tests/test_functions.py
new file mode 100644
index 00000000000..a352d69eb42
--- /dev/null
+++ b/contrib/python/jmespath/py3/tests/test_functions.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+from tests import unittest
+from datetime import datetime, timedelta
+import json
+
+import jmespath
+from jmespath import exceptions
+
+
+class TestFunctions(unittest.TestCase):
+
+ 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
+ # ability that ``to_string`` will always default to str()'ing values it
+ # doesn't understand.
+ data = [datetime.now(), datetime.now() + timedelta(seconds=1)]
+ result = jmespath.search('max([*].to_string(@))', data)
+ self.assertEqual(json.loads(result), str(data[-1]))
+
+ def test_type_error_messages(self):
+ with self.assertRaises(exceptions.JMESPathTypeError) as e:
+ jmespath.search('length(@)', 2)
+ exception = e.exception
+ # 1. Function name should be in error message
+ self.assertIn('length()', str(exception))
+ # 2. Mention it's an invalid type
+ self.assertIn('invalid type for value: 2', str(exception))
+ # 3. Mention the valid types:
+ self.assertIn("expected one of: ['string', 'array', 'object']",
+ str(exception))
+ # 4. Mention the actual type.
+ self.assertIn('received: "number"', str(exception))
+
+ def test_singular_in_error_message(self):
+ with self.assertRaises(exceptions.ArityError) as e:
+ jmespath.search('length(@, @)', [0, 1])
+ exception = e.exception
+ self.assertEqual(
+ str(exception),
+ 'Expected 1 argument for function length(), received 2')
+
+ def test_error_message_is_pluralized(self):
+ with self.assertRaises(exceptions.ArityError) as e:
+ jmespath.search('sort_by(@)', [0, 1])
+ exception = e.exception
+ self.assertEqual(
+ str(exception),
+ 'Expected 2 arguments for function sort_by(), received 1')
+
+ def test_variadic_is_pluralized(self):
+ with self.assertRaises(exceptions.VariadictArityError) as e:
+ jmespath.search('not_null()', 'foo')
+ exception = e.exception
+ self.assertEqual(
+ str(exception),
+ 'Expected at least 1 argument for function not_null(), received 0')
diff --git a/contrib/python/jmespath/py3/tests/test_lexer.py b/contrib/python/jmespath/py3/tests/test_lexer.py
new file mode 100644
index 00000000000..fbae0608813
--- /dev/null
+++ b/contrib/python/jmespath/py3/tests/test_lexer.py
@@ -0,0 +1,160 @@
+from tests import unittest
+
+from jmespath import lexer
+from jmespath.exceptions import LexerError, EmptyExpressionError
+
+
+class TestRegexLexer(unittest.TestCase):
+
+ def setUp(self):
+ self.lexer = lexer.Lexer()
+
+ def assert_tokens(self, actual, expected):
+ # The expected tokens only need to specify the
+ # type and value. The line/column numbers are not
+ # checked, and we use assertEqual for the tests
+ # that check those line numbers.
+ stripped = []
+ for item in actual:
+ stripped.append({'type': item['type'], 'value': item['value']})
+ # Every tokenization should end in eof, so we automatically
+ # check that value, strip it off the end, and then
+ # verify the remaining tokens against the expected.
+ # That way the tests don't need to add eof to every
+ # assert_tokens call.
+ self.assertEqual(stripped[-1]['type'], 'eof')
+ stripped.pop()
+ self.assertEqual(stripped, expected)
+
+ def test_empty_string(self):
+ with self.assertRaises(EmptyExpressionError):
+ list(self.lexer.tokenize(''))
+
+ def test_field(self):
+ tokens = list(self.lexer.tokenize('foo'))
+ self.assert_tokens(tokens, [{'type': 'unquoted_identifier',
+ 'value': 'foo'}])
+
+ def test_number(self):
+ tokens = list(self.lexer.tokenize('24'))
+ self.assert_tokens(tokens, [{'type': 'number',
+ 'value': 24}])
+
+ def test_negative_number(self):
+ tokens = list(self.lexer.tokenize('-24'))
+ self.assert_tokens(tokens, [{'type': 'number',
+ 'value': -24}])
+
+ def test_quoted_identifier(self):
+ tokens = list(self.lexer.tokenize('"foobar"'))
+ self.assert_tokens(tokens, [{'type': 'quoted_identifier',
+ 'value': "foobar"}])
+
+ def test_json_escaped_value(self):
+ tokens = list(self.lexer.tokenize('"\u2713"'))
+ self.assert_tokens(tokens, [{'type': 'quoted_identifier',
+ 'value': u"\u2713"}])
+
+ def test_number_expressions(self):
+ tokens = list(self.lexer.tokenize('foo.bar.baz'))
+ self.assert_tokens(tokens, [
+ {'type': 'unquoted_identifier', 'value': 'foo'},
+ {'type': 'dot', 'value': '.'},
+ {'type': 'unquoted_identifier', 'value': 'bar'},
+ {'type': 'dot', 'value': '.'},
+ {'type': 'unquoted_identifier', 'value': 'baz'},
+ ])
+
+ def test_space_separated(self):
+ tokens = list(self.lexer.tokenize('foo.bar[*].baz | a || b'))
+ self.assert_tokens(tokens, [
+ {'type': 'unquoted_identifier', 'value': 'foo'},
+ {'type': 'dot', 'value': '.'},
+ {'type': 'unquoted_identifier', 'value': 'bar'},
+ {'type': 'lbracket', 'value': '['},
+ {'type': 'star', 'value': '*'},
+ {'type': 'rbracket', 'value': ']'},
+ {'type': 'dot', 'value': '.'},
+ {'type': 'unquoted_identifier', 'value': 'baz'},
+ {'type': 'pipe', 'value': '|'},
+ {'type': 'unquoted_identifier', 'value': 'a'},
+ {'type': 'or', 'value': '||'},
+ {'type': 'unquoted_identifier', 'value': 'b'},
+ ])
+
+ def test_literal(self):
+ tokens = list(self.lexer.tokenize('`[0, 1]`'))
+ self.assert_tokens(tokens, [
+ {'type': 'literal', 'value': [0, 1]},
+ ])
+
+ def test_literal_string(self):
+ tokens = list(self.lexer.tokenize('`foobar`'))
+ self.assert_tokens(tokens, [
+ {'type': 'literal', 'value': "foobar"},
+ ])
+
+ def test_literal_number(self):
+ tokens = list(self.lexer.tokenize('`2`'))
+ self.assert_tokens(tokens, [
+ {'type': 'literal', 'value': 2},
+ ])
+
+ def test_literal_with_invalid_json(self):
+ with self.assertRaises(LexerError):
+ list(self.lexer.tokenize('`foo"bar`'))
+
+ def test_literal_with_empty_string(self):
+ tokens = list(self.lexer.tokenize('``'))
+ self.assert_tokens(tokens, [{'type': 'literal', 'value': ''}])
+
+ def test_position_information(self):
+ tokens = list(self.lexer.tokenize('foo'))
+ self.assertEqual(
+ tokens,
+ [{'type': 'unquoted_identifier', 'value': 'foo',
+ 'start': 0, 'end': 3},
+ {'type': 'eof', 'value': '', 'start': 3, 'end': 3}]
+ )
+
+ def test_position_multiple_tokens(self):
+ tokens = list(self.lexer.tokenize('foo.bar'))
+ self.assertEqual(
+ tokens,
+ [{'type': 'unquoted_identifier', 'value': 'foo',
+ 'start': 0, 'end': 3},
+ {'type': 'dot', 'value': '.',
+ 'start': 3, 'end': 4},
+ {'type': 'unquoted_identifier', 'value': 'bar',
+ 'start': 4, 'end': 7},
+ {'type': 'eof', 'value': '',
+ 'start': 7, 'end': 7},
+ ]
+ )
+
+ def test_adds_quotes_when_invalid_json(self):
+ tokens = list(self.lexer.tokenize('`{{}`'))
+ self.assertEqual(
+ tokens,
+ [{'type': 'literal', 'value': '{{}',
+ 'start': 0, 'end': 4},
+ {'type': 'eof', 'value': '',
+ 'start': 5, 'end': 5}
+ ]
+ )
+
+ def test_unknown_character(self):
+ with self.assertRaises(LexerError) as e:
+ tokens = list(self.lexer.tokenize('foo[0^]'))
+
+ def test_bad_first_character(self):
+ with self.assertRaises(LexerError):
+ tokens = list(self.lexer.tokenize('^foo[0]'))
+
+ def test_unknown_character_with_identifier(self):
+ with self.assertRaisesRegex(LexerError, "Unknown token"):
+ list(self.lexer.tokenize('foo-bar'))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/contrib/python/jmespath/py3/tests/test_search.py b/contrib/python/jmespath/py3/tests/test_search.py
new file mode 100644
index 00000000000..4832079ba71
--- /dev/null
+++ b/contrib/python/jmespath/py3/tests/test_search.py
@@ -0,0 +1,64 @@
+import sys
+import decimal
+from tests import unittest, OrderedDict
+
+import jmespath
+import jmespath.functions
+
+
+class TestSearchOptions(unittest.TestCase):
+ def test_can_provide_dict_cls(self):
+ result = jmespath.search(
+ '{a: a, b: b, c: c}.*',
+ {'c': 'c', 'b': 'b', 'a': 'a', 'd': 'd'},
+ options=jmespath.Options(dict_cls=OrderedDict))
+ self.assertEqual(result, ['a', 'b', 'c'])
+
+ def test_can_provide_custom_functions(self):
+ class CustomFunctions(jmespath.functions.Functions):
+ @jmespath.functions.signature(
+ {'types': ['number']},
+ {'types': ['number']})
+ def _func_custom_add(self, x, y):
+ return x + y
+
+ @jmespath.functions.signature(
+ {'types': ['number']},
+ {'types': ['number']})
+ def _func_my_subtract(self, x, y):
+ return x - y
+
+
+ options = jmespath.Options(custom_functions=CustomFunctions())
+ self.assertEqual(
+ jmespath.search('custom_add(`1`, `2`)', {}, options=options),
+ 3
+ )
+ self.assertEqual(
+ jmespath.search('my_subtract(`10`, `3`)', {}, options=options),
+ 7
+ )
+ # Should still be able to use the original functions without
+ # any interference from the CustomFunctions class.
+ self.assertEqual(
+ jmespath.search('length(`[1, 2]`)', {}), 2
+ )
+
+
+
+class TestPythonSpecificCases(unittest.TestCase):
+ def test_can_compare_strings(self):
+ # This is python specific behavior that's not in the official spec
+ # yet, but this was regression from 0.9.0 so it's been added back.
+ self.assertTrue(jmespath.search('a < b', {'a': '2016', 'b': '2017'}))
+
+ @unittest.skipIf(not hasattr(sys, 'maxint'), 'Test requires long() type')
+ def test_can_handle_long_ints(self):
+ result = sys.maxint + 1
+ self.assertEqual(jmespath.search('[?a >= `1`].a', [{'a': result}]),
+ [result])
+
+ def test_can_handle_decimals_as_numeric_type(self):
+ result = decimal.Decimal('3')
+ self.assertEqual(jmespath.search('[?a >= `1`].a', [{'a': result}]),
+ [result])
diff --git a/contrib/python/jmespath/py3/ya.make b/contrib/python/jmespath/py3/ya.make
index 8042d15beb1..eac0d048aa0 100644
--- a/contrib/python/jmespath/py3/ya.make
+++ b/contrib/python/jmespath/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(1.0.1)
+VERSION(1.1.0)
LICENSE(MIT)