diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/jmespath | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/jmespath')
-rw-r--r-- | contrib/python/jmespath/.dist-info/METADATA | 502 | ||||
-rw-r--r-- | contrib/python/jmespath/.dist-info/top_level.txt | 2 | ||||
-rw-r--r-- | contrib/python/jmespath/LICENSE.txt | 40 | ||||
-rw-r--r-- | contrib/python/jmespath/README.rst | 444 | ||||
-rw-r--r-- | contrib/python/jmespath/jmespath/__init__.py | 24 | ||||
-rw-r--r-- | contrib/python/jmespath/jmespath/parser.py | 2 | ||||
-rw-r--r-- | contrib/python/jmespath/jmespath/visitor.py | 6 | ||||
-rw-r--r-- | contrib/python/jmespath/tests/__init__.py | 80 | ||||
-rw-r--r-- | contrib/python/jmespath/tests/test_compliance.py | 204 | ||||
-rw-r--r-- | contrib/python/jmespath/tests/test_parser.py | 736 | ||||
-rw-r--r-- | contrib/python/jmespath/tests/ya.make | 34 | ||||
-rw-r--r-- | contrib/python/jmespath/ya.make | 40 |
12 files changed, 1057 insertions, 1057 deletions
diff --git a/contrib/python/jmespath/.dist-info/METADATA b/contrib/python/jmespath/.dist-info/METADATA index 78a973544b..ee91cf2412 100644 --- a/contrib/python/jmespath/.dist-info/METADATA +++ b/contrib/python/jmespath/.dist-info/METADATA @@ -1,251 +1,251 @@ -Metadata-Version: 2.0 -Name: jmespath -Version: 0.10.0 -Summary: JSON Matching Expressions -Home-page: https://github.com/jmespath/jmespath.py -Author: James Saryerwinnie -Author-email: js@jamesls.com -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 :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* - -JMESPath -======== - - -.. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/jmespath/chat - - -.. image:: https://travis-ci.org/jmespath/jmespath.py.svg?branch=develop - :target: https://travis-ci.org/jmespath/jmespath.py - - -.. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop - :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop - - -JMESPath (pronounced "james path") allows you to declaratively specify how to -extract elements from a JSON document. - -For example, given this document:: - - {"foo": {"bar": "baz"}} - -The jmespath expression ``foo.bar`` will return "baz". - -JMESPath also supports: - -Referencing elements in a list. Given the data:: - - {"foo": {"bar": ["one", "two"]}} - -The expression: ``foo.bar[0]`` will return "one". -You can also reference all the items in a list using the ``*`` -syntax:: - - {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} - -The expression: ``foo.bar[*].name`` will return ["one", "two"]. -Negative indexing is also supported (-1 refers to the last element -in the list). Given the data above, the expression -``foo.bar[-1].name`` will return "two". - -The ``*`` can also be used for hash types:: - - {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} - -The expression: ``foo.*.name`` will return ["one", "two"]. - - -Installation -============ - -You can install JMESPath from pypi with: - -.. code:: bash - - pip install jmespath - - -API -=== - -The ``jmespath.py`` library has two functions -that operate on python data structures. You can use ``search`` -and give it the jmespath expression and the data: - -.. code:: python - - >>> import jmespath - >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) - 'baz' - -Similar to the ``re`` module, you can use the ``compile`` function -to compile the JMESPath expression and use this parsed expression -to perform repeated searches: - -.. code:: python - - >>> import jmespath - >>> expression = jmespath.compile('foo.bar') - >>> expression.search({'foo': {'bar': 'baz'}}) - 'baz' - >>> expression.search({'foo': {'bar': 'other'}}) - 'other' - -This is useful if you're going to use the same jmespath expression to -search multiple documents. This avoids having to reparse the -JMESPath expression each time you search a new document. - -Options -------- - -You can provide an instance of ``jmespath.Options`` to control how -a JMESPath expression is evaluated. The most common scenario for -using an ``Options`` instance is if you want to have ordered output -of your dict keys. To do this you can use either of these options: - -.. code:: python - - >>> import jmespath - >>> jmespath.search('{a: a, b: b}', - ... mydata, - ... jmespath.Options(dict_cls=collections.OrderedDict)) - - - >>> import jmespath - >>> parsed = jmespath.compile('{a: a, b: b}') - >>> parsed.search(mydata, - ... jmespath.Options(dict_cls=collections.OrderedDict)) - - -Custom Functions -~~~~~~~~~~~~~~~~ - -The JMESPath language has numerous -`built-in functions -<http://jmespath.org/specification.html#built-in-functions>`__, but it is -also possible to add your own custom functions. Keep in mind that -custom function support in jmespath.py is experimental and the API may -change based on feedback. - -**If you have a custom function that you've found useful, consider submitting -it to jmespath.site and propose that it be added to the JMESPath language.** -You can submit proposals -`here <https://github.com/jmespath/jmespath.site/issues>`__. - -To create custom functions: - -* Create a subclass of ``jmespath.functions.Functions``. -* Create a method with the name ``_func_<your function name>``. -* Apply the ``jmespath.functions.signature`` decorator that indicates - the expected types of the function arguments. -* Provide an instance of your subclass in a ``jmespath.Options`` object. - -Below are a few examples: - -.. code:: python - - import jmespath - from jmespath import functions - - # 1. Create a subclass of functions.Functions. - # The function.Functions base class has logic - # that introspects all of its methods and automatically - # registers your custom functions in its function table. - class CustomFunctions(functions.Functions): - - # 2 and 3. Create a function that starts with _func_ - # and decorate it with @signature which indicates its - # expected types. - # In this example, we're creating a jmespath function - # called "unique_letters" that accepts a single argument - # with an expected type "string". - @functions.signature({'types': ['string']}) - def _func_unique_letters(self, s): - # Given a string s, return a sorted - # string of unique letters: 'ccbbadd' -> 'abcd' - return ''.join(sorted(set(s))) - - # Here's another example. This is creating - # a jmespath function called "my_add" that expects - # two arguments, both of which should be of type number. - @functions.signature({'types': ['number']}, {'types': ['number']}) - def _func_my_add(self, x, y): - return x + y - - # 4. Provide an instance of your subclass in a Options object. - options = jmespath.Options(custom_functions=CustomFunctions()) - - # Provide this value to jmespath.search: - # This will print 3 - print( - jmespath.search( - 'my_add(`1`, `2`)', {}, options=options) - ) - - # This will print "abcd" - print( - jmespath.search( - 'foo.bar | unique_letters(@)', - {'foo': {'bar': 'ccbbadd'}}, - options=options) - ) - -Again, if you come up with useful functions that you think make -sense in the JMESPath language (and make sense to implement in all -JMESPath libraries, not just python), please let us know at -`jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__. - - -Specification -============= - -If you'd like to learn more about the JMESPath language, you can check out -the `JMESPath tutorial <http://jmespath.org/tutorial.html>`__. Also check -out the `JMESPath examples page <http://jmespath.org/examples.html>`__ for -examples of more complex jmespath queries. - -The grammar is specified using ABNF, as described in -`RFC4234 <http://www.ietf.org/rfc/rfc4234.txt>`_. -You can find the most up to date -`grammar for JMESPath here <http://jmespath.org/specification.html#grammar>`__. - -You can read the full -`JMESPath specification here <http://jmespath.org/specification.html>`__. - - -Testing -======= - -In addition to the unit tests for the jmespath modules, -there is a ``tests/compliance`` directory that contains -.json files with test cases. This allows other implementations -to verify they are producing the correct output. Each json -file is grouped by feature. - - -Discuss -======= - -Join us on our `Gitter channel <https://gitter.im/jmespath/chat>`__ -if you want to chat or if you have any questions. - - +Metadata-Version: 2.0 +Name: jmespath +Version: 0.10.0 +Summary: JSON Matching Expressions +Home-page: https://github.com/jmespath/jmespath.py +Author: James Saryerwinnie +Author-email: js@jamesls.com +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 :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* + +JMESPath +======== + + +.. image:: https://badges.gitter.im/Join Chat.svg + :target: https://gitter.im/jmespath/chat + + +.. image:: https://travis-ci.org/jmespath/jmespath.py.svg?branch=develop + :target: https://travis-ci.org/jmespath/jmespath.py + + +.. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop + :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop + + +JMESPath (pronounced "james path") allows you to declaratively specify how to +extract elements from a JSON document. + +For example, given this document:: + + {"foo": {"bar": "baz"}} + +The jmespath expression ``foo.bar`` will return "baz". + +JMESPath also supports: + +Referencing elements in a list. Given the data:: + + {"foo": {"bar": ["one", "two"]}} + +The expression: ``foo.bar[0]`` will return "one". +You can also reference all the items in a list using the ``*`` +syntax:: + + {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} + +The expression: ``foo.bar[*].name`` will return ["one", "two"]. +Negative indexing is also supported (-1 refers to the last element +in the list). Given the data above, the expression +``foo.bar[-1].name`` will return "two". + +The ``*`` can also be used for hash types:: + + {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} + +The expression: ``foo.*.name`` will return ["one", "two"]. + + +Installation +============ + +You can install JMESPath from pypi with: + +.. code:: bash + + pip install jmespath + + +API +=== + +The ``jmespath.py`` library has two functions +that operate on python data structures. You can use ``search`` +and give it the jmespath expression and the data: + +.. code:: python + + >>> import jmespath + >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) + 'baz' + +Similar to the ``re`` module, you can use the ``compile`` function +to compile the JMESPath expression and use this parsed expression +to perform repeated searches: + +.. code:: python + + >>> import jmespath + >>> expression = jmespath.compile('foo.bar') + >>> expression.search({'foo': {'bar': 'baz'}}) + 'baz' + >>> expression.search({'foo': {'bar': 'other'}}) + 'other' + +This is useful if you're going to use the same jmespath expression to +search multiple documents. This avoids having to reparse the +JMESPath expression each time you search a new document. + +Options +------- + +You can provide an instance of ``jmespath.Options`` to control how +a JMESPath expression is evaluated. The most common scenario for +using an ``Options`` instance is if you want to have ordered output +of your dict keys. To do this you can use either of these options: + +.. code:: python + + >>> import jmespath + >>> jmespath.search('{a: a, b: b}', + ... mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + + + >>> import jmespath + >>> parsed = jmespath.compile('{a: a, b: b}') + >>> parsed.search(mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + + +Custom Functions +~~~~~~~~~~~~~~~~ + +The JMESPath language has numerous +`built-in functions +<http://jmespath.org/specification.html#built-in-functions>`__, but it is +also possible to add your own custom functions. Keep in mind that +custom function support in jmespath.py is experimental and the API may +change based on feedback. + +**If you have a custom function that you've found useful, consider submitting +it to jmespath.site and propose that it be added to the JMESPath language.** +You can submit proposals +`here <https://github.com/jmespath/jmespath.site/issues>`__. + +To create custom functions: + +* Create a subclass of ``jmespath.functions.Functions``. +* Create a method with the name ``_func_<your function name>``. +* Apply the ``jmespath.functions.signature`` decorator that indicates + the expected types of the function arguments. +* Provide an instance of your subclass in a ``jmespath.Options`` object. + +Below are a few examples: + +.. code:: python + + import jmespath + from jmespath import functions + + # 1. Create a subclass of functions.Functions. + # The function.Functions base class has logic + # that introspects all of its methods and automatically + # registers your custom functions in its function table. + class CustomFunctions(functions.Functions): + + # 2 and 3. Create a function that starts with _func_ + # and decorate it with @signature which indicates its + # expected types. + # In this example, we're creating a jmespath function + # called "unique_letters" that accepts a single argument + # with an expected type "string". + @functions.signature({'types': ['string']}) + def _func_unique_letters(self, s): + # Given a string s, return a sorted + # string of unique letters: 'ccbbadd' -> 'abcd' + return ''.join(sorted(set(s))) + + # Here's another example. This is creating + # a jmespath function called "my_add" that expects + # two arguments, both of which should be of type number. + @functions.signature({'types': ['number']}, {'types': ['number']}) + def _func_my_add(self, x, y): + return x + y + + # 4. Provide an instance of your subclass in a Options object. + options = jmespath.Options(custom_functions=CustomFunctions()) + + # Provide this value to jmespath.search: + # This will print 3 + print( + jmespath.search( + 'my_add(`1`, `2`)', {}, options=options) + ) + + # This will print "abcd" + print( + jmespath.search( + 'foo.bar | unique_letters(@)', + {'foo': {'bar': 'ccbbadd'}}, + options=options) + ) + +Again, if you come up with useful functions that you think make +sense in the JMESPath language (and make sense to implement in all +JMESPath libraries, not just python), please let us know at +`jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__. + + +Specification +============= + +If you'd like to learn more about the JMESPath language, you can check out +the `JMESPath tutorial <http://jmespath.org/tutorial.html>`__. Also check +out the `JMESPath examples page <http://jmespath.org/examples.html>`__ for +examples of more complex jmespath queries. + +The grammar is specified using ABNF, as described in +`RFC4234 <http://www.ietf.org/rfc/rfc4234.txt>`_. +You can find the most up to date +`grammar for JMESPath here <http://jmespath.org/specification.html#grammar>`__. + +You can read the full +`JMESPath specification here <http://jmespath.org/specification.html>`__. + + +Testing +======= + +In addition to the unit tests for the jmespath modules, +there is a ``tests/compliance`` directory that contains +.json files with test cases. This allows other implementations +to verify they are producing the correct output. Each json +file is grouped by feature. + + +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/.dist-info/top_level.txt b/contrib/python/jmespath/.dist-info/top_level.txt index 45c1e038e5..015d274622 100644 --- a/contrib/python/jmespath/.dist-info/top_level.txt +++ b/contrib/python/jmespath/.dist-info/top_level.txt @@ -1 +1 @@ -jmespath +jmespath diff --git a/contrib/python/jmespath/LICENSE.txt b/contrib/python/jmespath/LICENSE.txt index aa68928536..e3f59deed7 100644 --- a/contrib/python/jmespath/LICENSE.txt +++ b/contrib/python/jmespath/LICENSE.txt @@ -1,20 +1,20 @@ -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. +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/README.rst b/contrib/python/jmespath/README.rst index 530709edee..c49525b833 100644 --- a/contrib/python/jmespath/README.rst +++ b/contrib/python/jmespath/README.rst @@ -1,222 +1,222 @@ -JMESPath -======== - - -.. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/jmespath/chat - - -.. image:: https://travis-ci.org/jmespath/jmespath.py.svg?branch=develop - :target: https://travis-ci.org/jmespath/jmespath.py - - -.. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop - :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop - - -JMESPath (pronounced "james path") allows you to declaratively specify how to -extract elements from a JSON document. - -For example, given this document:: - - {"foo": {"bar": "baz"}} - -The jmespath expression ``foo.bar`` will return "baz". - -JMESPath also supports: - -Referencing elements in a list. Given the data:: - - {"foo": {"bar": ["one", "two"]}} - -The expression: ``foo.bar[0]`` will return "one". -You can also reference all the items in a list using the ``*`` -syntax:: - - {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} - -The expression: ``foo.bar[*].name`` will return ["one", "two"]. -Negative indexing is also supported (-1 refers to the last element -in the list). Given the data above, the expression -``foo.bar[-1].name`` will return "two". - -The ``*`` can also be used for hash types:: - - {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} - -The expression: ``foo.*.name`` will return ["one", "two"]. - - -Installation -============ - -You can install JMESPath from pypi with: - -.. code:: bash - - pip install jmespath - - -API -=== - -The ``jmespath.py`` library has two functions -that operate on python data structures. You can use ``search`` -and give it the jmespath expression and the data: - -.. code:: python - - >>> import jmespath - >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) - 'baz' - -Similar to the ``re`` module, you can use the ``compile`` function -to compile the JMESPath expression and use this parsed expression -to perform repeated searches: - -.. code:: python - - >>> import jmespath - >>> expression = jmespath.compile('foo.bar') - >>> expression.search({'foo': {'bar': 'baz'}}) - 'baz' - >>> expression.search({'foo': {'bar': 'other'}}) - 'other' - -This is useful if you're going to use the same jmespath expression to -search multiple documents. This avoids having to reparse the -JMESPath expression each time you search a new document. - -Options -------- - -You can provide an instance of ``jmespath.Options`` to control how -a JMESPath expression is evaluated. The most common scenario for -using an ``Options`` instance is if you want to have ordered output -of your dict keys. To do this you can use either of these options: - -.. code:: python - - >>> import jmespath - >>> jmespath.search('{a: a, b: b}', - ... mydata, - ... jmespath.Options(dict_cls=collections.OrderedDict)) - - - >>> import jmespath - >>> parsed = jmespath.compile('{a: a, b: b}') - >>> parsed.search(mydata, - ... jmespath.Options(dict_cls=collections.OrderedDict)) - - -Custom Functions -~~~~~~~~~~~~~~~~ - -The JMESPath language has numerous -`built-in functions -<http://jmespath.org/specification.html#built-in-functions>`__, but it is -also possible to add your own custom functions. Keep in mind that -custom function support in jmespath.py is experimental and the API may -change based on feedback. - -**If you have a custom function that you've found useful, consider submitting -it to jmespath.site and propose that it be added to the JMESPath language.** -You can submit proposals -`here <https://github.com/jmespath/jmespath.site/issues>`__. - -To create custom functions: - -* Create a subclass of ``jmespath.functions.Functions``. -* Create a method with the name ``_func_<your function name>``. -* Apply the ``jmespath.functions.signature`` decorator that indicates - the expected types of the function arguments. -* Provide an instance of your subclass in a ``jmespath.Options`` object. - -Below are a few examples: - -.. code:: python - - import jmespath - from jmespath import functions - - # 1. Create a subclass of functions.Functions. - # The function.Functions base class has logic - # that introspects all of its methods and automatically - # registers your custom functions in its function table. - class CustomFunctions(functions.Functions): - - # 2 and 3. Create a function that starts with _func_ - # and decorate it with @signature which indicates its - # expected types. - # In this example, we're creating a jmespath function - # called "unique_letters" that accepts a single argument - # with an expected type "string". - @functions.signature({'types': ['string']}) - def _func_unique_letters(self, s): - # Given a string s, return a sorted - # string of unique letters: 'ccbbadd' -> 'abcd' - return ''.join(sorted(set(s))) - - # Here's another example. This is creating - # a jmespath function called "my_add" that expects - # two arguments, both of which should be of type number. - @functions.signature({'types': ['number']}, {'types': ['number']}) - def _func_my_add(self, x, y): - return x + y - - # 4. Provide an instance of your subclass in a Options object. - options = jmespath.Options(custom_functions=CustomFunctions()) - - # Provide this value to jmespath.search: - # This will print 3 - print( - jmespath.search( - 'my_add(`1`, `2`)', {}, options=options) - ) - - # This will print "abcd" - print( - jmespath.search( - 'foo.bar | unique_letters(@)', - {'foo': {'bar': 'ccbbadd'}}, - options=options) - ) - -Again, if you come up with useful functions that you think make -sense in the JMESPath language (and make sense to implement in all -JMESPath libraries, not just python), please let us know at -`jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__. - - -Specification -============= - -If you'd like to learn more about the JMESPath language, you can check out -the `JMESPath tutorial <http://jmespath.org/tutorial.html>`__. Also check -out the `JMESPath examples page <http://jmespath.org/examples.html>`__ for -examples of more complex jmespath queries. - -The grammar is specified using ABNF, as described in -`RFC4234 <http://www.ietf.org/rfc/rfc4234.txt>`_. -You can find the most up to date -`grammar for JMESPath here <http://jmespath.org/specification.html#grammar>`__. - -You can read the full -`JMESPath specification here <http://jmespath.org/specification.html>`__. - - -Testing -======= - -In addition to the unit tests for the jmespath modules, -there is a ``tests/compliance`` directory that contains -.json files with test cases. This allows other implementations -to verify they are producing the correct output. Each json -file is grouped by feature. - - -Discuss -======= - -Join us on our `Gitter channel <https://gitter.im/jmespath/chat>`__ -if you want to chat or if you have any questions. +JMESPath +======== + + +.. image:: https://badges.gitter.im/Join Chat.svg + :target: https://gitter.im/jmespath/chat + + +.. image:: https://travis-ci.org/jmespath/jmespath.py.svg?branch=develop + :target: https://travis-ci.org/jmespath/jmespath.py + + +.. image:: https://codecov.io/github/jmespath/jmespath.py/coverage.svg?branch=develop + :target: https://codecov.io/github/jmespath/jmespath.py?branch=develop + + +JMESPath (pronounced "james path") allows you to declaratively specify how to +extract elements from a JSON document. + +For example, given this document:: + + {"foo": {"bar": "baz"}} + +The jmespath expression ``foo.bar`` will return "baz". + +JMESPath also supports: + +Referencing elements in a list. Given the data:: + + {"foo": {"bar": ["one", "two"]}} + +The expression: ``foo.bar[0]`` will return "one". +You can also reference all the items in a list using the ``*`` +syntax:: + + {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} + +The expression: ``foo.bar[*].name`` will return ["one", "two"]. +Negative indexing is also supported (-1 refers to the last element +in the list). Given the data above, the expression +``foo.bar[-1].name`` will return "two". + +The ``*`` can also be used for hash types:: + + {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} + +The expression: ``foo.*.name`` will return ["one", "two"]. + + +Installation +============ + +You can install JMESPath from pypi with: + +.. code:: bash + + pip install jmespath + + +API +=== + +The ``jmespath.py`` library has two functions +that operate on python data structures. You can use ``search`` +and give it the jmespath expression and the data: + +.. code:: python + + >>> import jmespath + >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) + 'baz' + +Similar to the ``re`` module, you can use the ``compile`` function +to compile the JMESPath expression and use this parsed expression +to perform repeated searches: + +.. code:: python + + >>> import jmespath + >>> expression = jmespath.compile('foo.bar') + >>> expression.search({'foo': {'bar': 'baz'}}) + 'baz' + >>> expression.search({'foo': {'bar': 'other'}}) + 'other' + +This is useful if you're going to use the same jmespath expression to +search multiple documents. This avoids having to reparse the +JMESPath expression each time you search a new document. + +Options +------- + +You can provide an instance of ``jmespath.Options`` to control how +a JMESPath expression is evaluated. The most common scenario for +using an ``Options`` instance is if you want to have ordered output +of your dict keys. To do this you can use either of these options: + +.. code:: python + + >>> import jmespath + >>> jmespath.search('{a: a, b: b}', + ... mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + + + >>> import jmespath + >>> parsed = jmespath.compile('{a: a, b: b}') + >>> parsed.search(mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + + +Custom Functions +~~~~~~~~~~~~~~~~ + +The JMESPath language has numerous +`built-in functions +<http://jmespath.org/specification.html#built-in-functions>`__, but it is +also possible to add your own custom functions. Keep in mind that +custom function support in jmespath.py is experimental and the API may +change based on feedback. + +**If you have a custom function that you've found useful, consider submitting +it to jmespath.site and propose that it be added to the JMESPath language.** +You can submit proposals +`here <https://github.com/jmespath/jmespath.site/issues>`__. + +To create custom functions: + +* Create a subclass of ``jmespath.functions.Functions``. +* Create a method with the name ``_func_<your function name>``. +* Apply the ``jmespath.functions.signature`` decorator that indicates + the expected types of the function arguments. +* Provide an instance of your subclass in a ``jmespath.Options`` object. + +Below are a few examples: + +.. code:: python + + import jmespath + from jmespath import functions + + # 1. Create a subclass of functions.Functions. + # The function.Functions base class has logic + # that introspects all of its methods and automatically + # registers your custom functions in its function table. + class CustomFunctions(functions.Functions): + + # 2 and 3. Create a function that starts with _func_ + # and decorate it with @signature which indicates its + # expected types. + # In this example, we're creating a jmespath function + # called "unique_letters" that accepts a single argument + # with an expected type "string". + @functions.signature({'types': ['string']}) + def _func_unique_letters(self, s): + # Given a string s, return a sorted + # string of unique letters: 'ccbbadd' -> 'abcd' + return ''.join(sorted(set(s))) + + # Here's another example. This is creating + # a jmespath function called "my_add" that expects + # two arguments, both of which should be of type number. + @functions.signature({'types': ['number']}, {'types': ['number']}) + def _func_my_add(self, x, y): + return x + y + + # 4. Provide an instance of your subclass in a Options object. + options = jmespath.Options(custom_functions=CustomFunctions()) + + # Provide this value to jmespath.search: + # This will print 3 + print( + jmespath.search( + 'my_add(`1`, `2`)', {}, options=options) + ) + + # This will print "abcd" + print( + jmespath.search( + 'foo.bar | unique_letters(@)', + {'foo': {'bar': 'ccbbadd'}}, + options=options) + ) + +Again, if you come up with useful functions that you think make +sense in the JMESPath language (and make sense to implement in all +JMESPath libraries, not just python), please let us know at +`jmespath.site <https://github.com/jmespath/jmespath.site/issues>`__. + + +Specification +============= + +If you'd like to learn more about the JMESPath language, you can check out +the `JMESPath tutorial <http://jmespath.org/tutorial.html>`__. Also check +out the `JMESPath examples page <http://jmespath.org/examples.html>`__ for +examples of more complex jmespath queries. + +The grammar is specified using ABNF, as described in +`RFC4234 <http://www.ietf.org/rfc/rfc4234.txt>`_. +You can find the most up to date +`grammar for JMESPath here <http://jmespath.org/specification.html#grammar>`__. + +You can read the full +`JMESPath specification here <http://jmespath.org/specification.html>`__. + + +Testing +======= + +In addition to the unit tests for the jmespath modules, +there is a ``tests/compliance`` directory that contains +.json files with test cases. This allows other implementations +to verify they are producing the correct output. Each json +file is grouped by feature. + + +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/jmespath/__init__.py b/contrib/python/jmespath/jmespath/__init__.py index 99482dba8e..c169c8754a 100644 --- a/contrib/python/jmespath/jmespath/__init__.py +++ b/contrib/python/jmespath/jmespath/__init__.py @@ -1,20 +1,20 @@ -import warnings -import sys +import warnings +import sys from jmespath import parser from jmespath.visitor import Options -__version__ = '0.10.0' - - -if sys.version_info[:2] <= (2, 6) or ((3, 0) <= sys.version_info[:2] <= (3, 3)): - python_ver = '.'.join(str(x) for x in sys.version_info[:3]) - - warnings.warn( - 'You are using Python {0}, which will no longer be supported in ' - 'version 0.11.0'.format(python_ver), - DeprecationWarning) +__version__ = '0.10.0' +if sys.version_info[:2] <= (2, 6) or ((3, 0) <= sys.version_info[:2] <= (3, 3)): + python_ver = '.'.join(str(x) for x in sys.version_info[:3]) + + warnings.warn( + 'You are using Python {0}, which will no longer be supported in ' + 'version 0.11.0'.format(python_ver), + DeprecationWarning) + + def compile(expression): return parser.Parser().parse(expression) diff --git a/contrib/python/jmespath/jmespath/parser.py b/contrib/python/jmespath/jmespath/parser.py index eeac38fa89..9a8877c668 100644 --- a/contrib/python/jmespath/jmespath/parser.py +++ b/contrib/python/jmespath/jmespath/parser.py @@ -490,7 +490,7 @@ class Parser(object): def _free_cache_entries(self): for key in random.sample(self._CACHE.keys(), int(self._MAX_SIZE / 2)): - self._CACHE.pop(key, None) + self._CACHE.pop(key, None) @classmethod def purge(cls): diff --git a/contrib/python/jmespath/jmespath/visitor.py b/contrib/python/jmespath/jmespath/visitor.py index b3e846b761..91cce0e6a5 100644 --- a/contrib/python/jmespath/jmespath/visitor.py +++ b/contrib/python/jmespath/jmespath/visitor.py @@ -29,9 +29,9 @@ def _is_special_integer_case(x, y): # Also need to consider that: # >>> 0 in [True, False] # True - if type(x) is int and (x == 0 or x == 1): + if type(x) is int and (x == 0 or x == 1): return y is True or y is False - elif type(y) is int and (y == 0 or y == 1): + elif type(y) is int and (y == 0 or y == 1): return x is True or x is False @@ -257,7 +257,7 @@ class TreeInterpreter(Visitor): def visit_not_expression(self, node, value): original_result = self.visit(node['children'][0], value) - if type(original_result) is int and original_result == 0: + if type(original_result) is int and original_result == 0: # Special case for 0, !0 should be false, not true. # 0 is not a special cased integer in jmespath. return False diff --git a/contrib/python/jmespath/tests/__init__.py b/contrib/python/jmespath/tests/__init__.py index d86946ccda..7dd0ef61bd 100644 --- a/contrib/python/jmespath/tests/__init__.py +++ b/contrib/python/jmespath/tests/__init__.py @@ -1,40 +1,40 @@ -import sys -from jmespath import ast - - -# The unittest module got a significant overhaul -# in 2.7, so if we're in 2.6 we can use the backported -# version unittest2. -if sys.version_info[:2] == (2, 6): - import unittest2 as unittest - import simplejson as json - from ordereddict import OrderedDict -else: - import unittest - import json - from collections import OrderedDict - - -# Helper method used to create an s-expression -# of the AST to make unit test assertions easier. -# You get a nice string diff on assert failures. -def as_s_expression(node): - parts = [] - _as_s_expression(node, parts) - return ''.join(parts) - - -def _as_s_expression(node, parts): - parts.append("(%s" % (node.__class__.__name__.lower())) - if isinstance(node, ast.Field): - parts.append(" %s" % node.name) - elif isinstance(node, ast.FunctionExpression): - parts.append(" %s" % node.name) - elif isinstance(node, ast.KeyValPair): - parts.append(" %s" % node.key_name) - for child in node.children: - parts.append(" ") - _as_s_expression(child, parts) - parts.append(")") - - +import sys +from jmespath import ast + + +# The unittest module got a significant overhaul +# in 2.7, so if we're in 2.6 we can use the backported +# version unittest2. +if sys.version_info[:2] == (2, 6): + import unittest2 as unittest + import simplejson as json + from ordereddict import OrderedDict +else: + import unittest + import json + from collections import OrderedDict + + +# Helper method used to create an s-expression +# of the AST to make unit test assertions easier. +# You get a nice string diff on assert failures. +def as_s_expression(node): + parts = [] + _as_s_expression(node, parts) + return ''.join(parts) + + +def _as_s_expression(node, parts): + parts.append("(%s" % (node.__class__.__name__.lower())) + if isinstance(node, ast.Field): + parts.append(" %s" % node.name) + elif isinstance(node, ast.FunctionExpression): + parts.append(" %s" % node.name) + elif isinstance(node, ast.KeyValPair): + parts.append(" %s" % node.key_name) + for child in node.children: + parts.append(" ") + _as_s_expression(child, parts) + parts.append(")") + + diff --git a/contrib/python/jmespath/tests/test_compliance.py b/contrib/python/jmespath/tests/test_compliance.py index 86e8297027..9b79a8fde9 100644 --- a/contrib/python/jmespath/tests/test_compliance.py +++ b/contrib/python/jmespath/tests/test_compliance.py @@ -1,65 +1,65 @@ -import os +import os import pytest -from pprint import pformat -from . import OrderedDict -from . import json - -from jmespath.visitor import Options - - -TEST_DIR = os.path.dirname(os.path.abspath(__file__)) -COMPLIANCE_DIR = os.path.join(TEST_DIR, 'compliance') -LEGACY_DIR = os.path.join(TEST_DIR, 'legacy') -NOT_SPECIFIED = object() -OPTIONS = Options(dict_cls=OrderedDict) - - +from pprint import pformat +from . import OrderedDict +from . import json + +from jmespath.visitor import Options + + +TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +COMPLIANCE_DIR = os.path.join(TEST_DIR, 'compliance') +LEGACY_DIR = os.path.join(TEST_DIR, 'legacy') +NOT_SPECIFIED = object() +OPTIONS = Options(dict_cls=OrderedDict) + + def _load_all_cases(): - for full_path in _walk_files(): - if full_path.endswith('.json'): - for given, test_type, test_data in load_cases(full_path): - t = test_data - # Benchmark tests aren't run as part of the normal - # test suite, so we only care about 'result' and - # 'error' test_types. - if test_type == 'result': + for full_path in _walk_files(): + if full_path.endswith('.json'): + for given, test_type, test_data in load_cases(full_path): + t = test_data + # Benchmark tests aren't run as part of the normal + # test suite, so we only care about 'result' and + # 'error' test_types. + if test_type == 'result': yield (given, t['expression'], t['result'], os.path.basename(full_path)) - elif test_type == 'error': + elif test_type == 'error': yield (given, t['expression'], t['error'], os.path.basename(full_path)) - - -def _walk_files(): - # Check for a shortcut when running the tests interactively. - # If a JMESPATH_TEST is defined, that file is used as the - # only test to run. Useful when doing feature development. - single_file = os.environ.get('JMESPATH_TEST') - if single_file is not None: - yield os.path.abspath(single_file) - else: - for root, dirnames, filenames in os.walk(TEST_DIR): - for filename in filenames: - yield os.path.join(root, filename) - for root, dirnames, filenames in os.walk(LEGACY_DIR): - for filename in filenames: - yield os.path.join(root, filename) - - -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) - - + + +def _walk_files(): + # Check for a shortcut when running the tests interactively. + # If a JMESPATH_TEST is defined, that file is used as the + # only test to run. Useful when doing feature development. + single_file = os.environ.get('JMESPATH_TEST') + if single_file is not None: + yield os.path.abspath(single_file) + else: + for root, dirnames, filenames in os.walk(TEST_DIR): + for filename in filenames: + yield os.path.join(root, filename) + for root, dirnames, filenames in os.walk(LEGACY_DIR): + for filename in filenames: + yield os.path.join(root, filename) + + +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) + + @pytest.mark.parametrize( 'given,expression,expected,filename', list(_load_all_cases()) @@ -68,47 +68,47 @@ def test_compliance(given, expression, expected, filename): _test_expression(given, expression, expected, filename) -def _test_expression(given, expression, expected, filename): - import jmespath.parser - try: - parsed = jmespath.compile(expression) - except ValueError as e: - raise AssertionError( - 'jmespath expression failed to compile: "%s", error: %s"' % - (expression, e)) - actual = parsed.search(given, options=OPTIONS) - expected_repr = json.dumps(expected, indent=4) - actual_repr = json.dumps(actual, indent=4) - error_msg = ("\n\n (%s) The expression '%s' was suppose to give:\n%s\n" - "Instead it matched:\n%s\nparsed as:\n%s\ngiven:\n%s" % ( - filename, expression, expected_repr, - actual_repr, pformat(parsed.parsed), - json.dumps(given, indent=4))) - error_msg = error_msg.replace(r'\n', '\n') - assert actua == expected, error_msg - - -def _test_error_expression(given, expression, error, filename): - import jmespath.parser - if error not in ('syntax', 'invalid-type', - 'unknown-function', 'invalid-arity', 'invalid-value'): - raise RuntimeError("Unknown error type '%s'" % error) - try: - parsed = jmespath.compile(expression) - parsed.search(given) - except ValueError: - # Test passes, it raised a parse error as expected. - pass - except Exception as e: - # Failure because an unexpected exception was raised. - error_msg = ("\n\n (%s) The expression '%s' was suppose to be a " - "syntax error, but it raised an unexpected error:\n\n%s" % ( - filename, expression, e)) - error_msg = error_msg.replace(r'\n', '\n') - raise AssertionError(error_msg) - else: - error_msg = ("\n\n (%s) The expression '%s' was suppose to be a " - "syntax error, but it successfully parsed as:\n\n%s" % ( - filename, expression, pformat(parsed.parsed))) - error_msg = error_msg.replace(r'\n', '\n') - raise AssertionError(error_msg) +def _test_expression(given, expression, expected, filename): + import jmespath.parser + try: + parsed = jmespath.compile(expression) + except ValueError as e: + raise AssertionError( + 'jmespath expression failed to compile: "%s", error: %s"' % + (expression, e)) + actual = parsed.search(given, options=OPTIONS) + expected_repr = json.dumps(expected, indent=4) + actual_repr = json.dumps(actual, indent=4) + error_msg = ("\n\n (%s) The expression '%s' was suppose to give:\n%s\n" + "Instead it matched:\n%s\nparsed as:\n%s\ngiven:\n%s" % ( + filename, expression, expected_repr, + actual_repr, pformat(parsed.parsed), + json.dumps(given, indent=4))) + error_msg = error_msg.replace(r'\n', '\n') + assert actua == expected, error_msg + + +def _test_error_expression(given, expression, error, filename): + import jmespath.parser + if error not in ('syntax', 'invalid-type', + 'unknown-function', 'invalid-arity', 'invalid-value'): + raise RuntimeError("Unknown error type '%s'" % error) + try: + parsed = jmespath.compile(expression) + parsed.search(given) + except ValueError: + # Test passes, it raised a parse error as expected. + pass + except Exception as e: + # Failure because an unexpected exception was raised. + error_msg = ("\n\n (%s) The expression '%s' was suppose to be a " + "syntax error, but it raised an unexpected error:\n\n%s" % ( + filename, expression, e)) + error_msg = error_msg.replace(r'\n', '\n') + raise AssertionError(error_msg) + else: + error_msg = ("\n\n (%s) The expression '%s' was suppose to be a " + "syntax error, but it successfully parsed as:\n\n%s" % ( + filename, expression, pformat(parsed.parsed))) + error_msg = error_msg.replace(r'\n', '\n') + raise AssertionError(error_msg) diff --git a/contrib/python/jmespath/tests/test_parser.py b/contrib/python/jmespath/tests/test_parser.py index 121b4b79b2..6abe0f42c3 100644 --- a/contrib/python/jmespath/tests/test_parser.py +++ b/contrib/python/jmespath/tests/test_parser.py @@ -1,368 +1,368 @@ -#!/usr/bin/env python - -import re -from . import unittest, OrderedDict - -from jmespath import parser -from jmespath import visitor -from jmespath import ast -from jmespath import exceptions - - -class TestParser(unittest.TestCase): - def setUp(self): - self.parser = parser.Parser() - - def assert_parsed_ast(self, expression, expected_ast): - parsed = self.parser.parse(expression) - self.assertEqual(parsed.parsed, expected_ast) - - def test_parse_empty_string_raises_exception(self): - with self.assertRaises(exceptions.EmptyExpressionError): - self.parser.parse('') - - def test_field(self): - self.assert_parsed_ast('foo', ast.field('foo')) - - def test_dot_syntax(self): - self.assert_parsed_ast('foo.bar', - ast.subexpression([ast.field('foo'), - ast.field('bar')])) - - def test_multiple_dots(self): - parsed = self.parser.parse('foo.bar.baz') - self.assertEqual( - parsed.search({'foo': {'bar': {'baz': 'correct'}}}), 'correct') - - def test_index(self): - parsed = self.parser.parse('foo[1]') - self.assertEqual( - parsed.search({'foo': ['zero', 'one', 'two']}), - 'one') - - def test_quoted_subexpression(self): - self.assert_parsed_ast('"foo"."bar"', - ast.subexpression([ - ast.field('foo'), - ast.field('bar')])) - - def test_wildcard(self): - parsed = self.parser.parse('foo[*]') - self.assertEqual( - parsed.search({'foo': ['zero', 'one', 'two']}), - ['zero', 'one', 'two']) - - def test_wildcard_with_children(self): - parsed = self.parser.parse('foo[*].bar') - self.assertEqual( - parsed.search({'foo': [{'bar': 'one'}, {'bar': 'two'}]}), - ['one', 'two']) - - def test_or_expression(self): - parsed = self.parser.parse('foo || bar') - self.assertEqual(parsed.search({'foo': 'foo'}), 'foo') - self.assertEqual(parsed.search({'bar': 'bar'}), 'bar') - self.assertEqual(parsed.search({'foo': 'foo', 'bar': 'bar'}), 'foo') - self.assertEqual(parsed.search({'bad': 'bad'}), None) - - def test_complex_or_expression(self): - parsed = self.parser.parse('foo.foo || foo.bar') - self.assertEqual(parsed.search({'foo': {'foo': 'foo'}}), 'foo') - self.assertEqual(parsed.search({'foo': {'bar': 'bar'}}), 'bar') - self.assertEqual(parsed.search({'foo': {'baz': 'baz'}}), None) - - def test_or_repr(self): - self.assert_parsed_ast('foo || bar', ast.or_expression(ast.field('foo'), - ast.field('bar'))) - - def test_unicode_literals_escaped(self): - self.assert_parsed_ast(r'`"\u2713"`', ast.literal(u'\u2713')) - - def test_multiselect(self): - parsed = self.parser.parse('foo.{bar: bar,baz: baz}') - self.assertEqual( - parsed.search({'foo': {'bar': 'bar', 'baz': 'baz', 'qux': 'qux'}}), - {'bar': 'bar', 'baz': 'baz'}) - - def test_multiselect_subexpressions(self): - parsed = self.parser.parse('foo.{"bar.baz": bar.baz, qux: qux}') - self.assertEqual( - parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, 'qux': 'qux'}}), - {'bar.baz': 'CORRECT', 'qux': 'qux'}) - - def test_multiselect_with_all_quoted_keys(self): - parsed = self.parser.parse('foo.{"bar": bar.baz, "qux": qux}') - result = parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, 'qux': 'qux'}}) - self.assertEqual(result, {"bar": "CORRECT", "qux": "qux"}) - - def test_function_call_with_and_statement(self): - self.assert_parsed_ast( - 'f(@ && @)', - {'children': [{'children': [{'children': [], 'type': 'current'}, - {'children': [], 'type': 'current'}], - 'type': 'and_expression'}], - 'type': 'function_expression', - 'value': 'f'}) - - -class TestErrorMessages(unittest.TestCase): - - def setUp(self): - self.parser = parser.Parser() - - def assert_error_message(self, expression, error_message, - exception=exceptions.ParseError): - try: - self.parser.parse(expression) - except exception as e: - self.assertEqual(error_message, str(e)) - return - except Exception as e: - self.fail( - "Unexpected error raised (%s: %s) for bad expression: %s" % - (e.__class__.__name__, e, expression)) - else: - self.fail( - "ParseError not raised for bad expression: %s" % expression) - - def test_bad_parse(self): - with self.assertRaises(exceptions.ParseError): - self.parser.parse('foo]baz') - - def test_bad_parse_error_message(self): - error_message = ( - 'Unexpected token: ]: Parse error at column 3, ' - 'token "]" (RBRACKET), for expression:\n' - '"foo]baz"\n' - ' ^') - self.assert_error_message('foo]baz', error_message) - - def test_bad_parse_error_message_with_multiselect(self): - error_message = ( - 'Invalid jmespath expression: Incomplete expression:\n' - '"foo.{bar: baz,bar: bar"\n' - ' ^') - self.assert_error_message('foo.{bar: baz,bar: bar', error_message) - - def test_incomplete_expression_with_missing_paren(self): - error_message = ( - 'Invalid jmespath expression: Incomplete expression:\n' - '"length(@,"\n' - ' ^') - self.assert_error_message('length(@,', error_message) - - def test_bad_lexer_values(self): - error_message = ( - 'Bad jmespath expression: ' - 'Unclosed " delimiter:\n' - 'foo."bar\n' - ' ^') - self.assert_error_message('foo."bar', error_message, - exception=exceptions.LexerError) - - def test_bad_unicode_string(self): - # This error message is straight from the JSON parser - # and pypy has a slightly different error message, - # so we're not using assert_error_message. - error_message = re.compile( - r'Bad jmespath expression: ' - r'Invalid \\uXXXX escape.*\\uAZ12', re.DOTALL) - with self.assertRaisesRegexp(exceptions.LexerError, error_message): - self.parser.parse(r'"\uAZ12"') - - -class TestParserWildcards(unittest.TestCase): - def setUp(self): - self.parser = parser.Parser() - self.data = { - 'foo': [ - {'bar': [{'baz': 'one'}, {'baz': 'two'}]}, - {'bar': [{'baz': 'three'}, {'baz': 'four'}, {'baz': 'five'}]}, - ] - } - - def test_multiple_index_wildcards(self): - parsed = self.parser.parse('foo[*].bar[*].baz') - self.assertEqual(parsed.search(self.data), - [['one', 'two'], ['three', 'four', 'five']]) - - def test_wildcard_mix_with_indices(self): - parsed = self.parser.parse('foo[*].bar[0].baz') - self.assertEqual(parsed.search(self.data), - ['one', 'three']) - - def test_wildcard_mix_last(self): - parsed = self.parser.parse('foo[0].bar[*].baz') - self.assertEqual(parsed.search(self.data), - ['one', 'two']) - - def test_indices_out_of_bounds(self): - parsed = self.parser.parse('foo[*].bar[2].baz') - self.assertEqual(parsed.search(self.data), - ['five']) - - def test_root_indices(self): - parsed = self.parser.parse('[0]') - self.assertEqual(parsed.search(['one', 'two']), 'one') - - def test_root_wildcard(self): - parsed = self.parser.parse('*.foo') - data = {'top1': {'foo': 'bar'}, 'top2': {'foo': 'baz'}, - 'top3': {'notfoo': 'notfoo'}} - # Sorted is being used because the order of the keys are not - # required to be in any specific order. - self.assertEqual(sorted(parsed.search(data)), sorted(['bar', 'baz'])) - self.assertEqual(sorted(self.parser.parse('*.notfoo').search(data)), - sorted(['notfoo'])) - - def test_only_wildcard(self): - parsed = self.parser.parse('*') - data = {'foo': 'a', 'bar': 'b', 'baz': 'c'} - self.assertEqual(sorted(parsed.search(data)), sorted(['a', 'b', 'c'])) - - def test_escape_sequences(self): - self.assertEqual(self.parser.parse(r'"foo\tbar"').search( - {'foo\tbar': 'baz'}), 'baz') - self.assertEqual(self.parser.parse(r'"foo\nbar"').search( - {'foo\nbar': 'baz'}), 'baz') - self.assertEqual(self.parser.parse(r'"foo\bbar"').search( - {'foo\bbar': 'baz'}), 'baz') - self.assertEqual(self.parser.parse(r'"foo\fbar"').search( - {'foo\fbar': 'baz'}), 'baz') - self.assertEqual(self.parser.parse(r'"foo\rbar"').search( - {'foo\rbar': 'baz'}), 'baz') - - def test_consecutive_escape_sequences(self): - parsed = self.parser.parse(r'"foo\\nbar"') - self.assertEqual(parsed.search({'foo\\nbar': 'baz'}), 'baz') - - parsed = self.parser.parse(r'"foo\n\t\rbar"') - self.assertEqual(parsed.search({'foo\n\t\rbar': 'baz'}), 'baz') - - def test_escape_sequence_at_end_of_string_not_allowed(self): - with self.assertRaises(ValueError): - self.parser.parse('foobar\\') - - def test_wildcard_with_multiselect(self): - parsed = self.parser.parse('foo.*.{a: a, b: b}') - data = { - 'foo': { - 'one': { - 'a': {'c': 'CORRECT', 'd': 'other'}, - 'b': {'c': 'ALSOCORRECT', 'd': 'other'}, - }, - 'two': { - 'a': {'c': 'CORRECT', 'd': 'other'}, - 'c': {'c': 'WRONG', 'd': 'other'}, - }, - } - } - match = parsed.search(data) - self.assertEqual(len(match), 2) - self.assertIn('a', match[0]) - self.assertIn('b', match[0]) - self.assertIn('a', match[1]) - self.assertIn('b', match[1]) - - -class TestMergedLists(unittest.TestCase): - def setUp(self): - self.parser = parser.Parser() - self.data = { - "foo": [ - [["one", "two"], ["three", "four"]], - [["five", "six"], ["seven", "eight"]], - [["nine"], ["ten"]] - ] - } - - def test_merge_with_indices(self): - parsed = self.parser.parse('foo[][0]') - match = parsed.search(self.data) - self.assertEqual(match, ["one", "three", "five", "seven", - "nine", "ten"]) - - def test_trailing_merged_operator(self): - parsed = self.parser.parse('foo[]') - match = parsed.search(self.data) - self.assertEqual( - match, - [["one", "two"], ["three", "four"], - ["five", "six"], ["seven", "eight"], - ["nine"], ["ten"]]) - - -class TestParserCaching(unittest.TestCase): - def test_compile_lots_of_expressions(self): - # We have to be careful here because this is an implementation detail - # that should be abstracted from the user, but we need to make sure we - # exercise the code and that it doesn't blow up. - p = parser.Parser() - compiled = [] - compiled2 = [] - for i in range(parser.Parser._MAX_SIZE + 1): - compiled.append(p.parse('foo%s' % i)) - # Rerun the test and half of these entries should be from the - # cache but they should still be equal to compiled. - for i in range(parser.Parser._MAX_SIZE + 1): - compiled2.append(p.parse('foo%s' % i)) - self.assertEqual(len(compiled), len(compiled2)) - self.assertEqual( - [expr.parsed for expr in compiled], - [expr.parsed for expr in compiled2]) - - def test_cache_purge(self): - p = parser.Parser() - first = p.parse('foo') - cached = p.parse('foo') - p.purge() - second = p.parse('foo') - self.assertEqual(first.parsed, - second.parsed) - self.assertEqual(first.parsed, - cached.parsed) - - -class TestParserAddsExpressionAttribute(unittest.TestCase): - def test_expression_available_from_parser(self): - p = parser.Parser() - parsed = p.parse('foo.bar') - self.assertEqual(parsed.expression, 'foo.bar') - - -class TestParsedResultAddsOptions(unittest.TestCase): - def test_can_have_ordered_dict(self): - p = parser.Parser() - parsed = p.parse('{a: a, b: b, c: c}') - options = visitor.Options(dict_cls=OrderedDict) - result = parsed.search( - {"c": "c", "b": "b", "a": "a"}, options=options) - # The order should be 'a', 'b' because we're using an - # OrderedDict - self.assertEqual(list(result), ['a', 'b', 'c']) - - -class TestRenderGraphvizFile(unittest.TestCase): - def test_dot_file_rendered(self): - p = parser.Parser() - result = p.parse('foo') - dot_contents = result._render_dot_file() - self.assertEqual(dot_contents, - 'digraph AST {\nfield1 [label="field(foo)"]\n}') - - def test_dot_file_subexpr(self): - p = parser.Parser() - result = p.parse('foo.bar') - dot_contents = result._render_dot_file() - self.assertEqual( - dot_contents, - 'digraph AST {\n' - 'subexpression1 [label="subexpression()"]\n' - ' subexpression1 -> field2\n' - 'field2 [label="field(foo)"]\n' - ' subexpression1 -> field3\n' - 'field3 [label="field(bar)"]\n}') - - -if __name__ == '__main__': - unittest.main() +#!/usr/bin/env python + +import re +from . import unittest, OrderedDict + +from jmespath import parser +from jmespath import visitor +from jmespath import ast +from jmespath import exceptions + + +class TestParser(unittest.TestCase): + def setUp(self): + self.parser = parser.Parser() + + def assert_parsed_ast(self, expression, expected_ast): + parsed = self.parser.parse(expression) + self.assertEqual(parsed.parsed, expected_ast) + + def test_parse_empty_string_raises_exception(self): + with self.assertRaises(exceptions.EmptyExpressionError): + self.parser.parse('') + + def test_field(self): + self.assert_parsed_ast('foo', ast.field('foo')) + + def test_dot_syntax(self): + self.assert_parsed_ast('foo.bar', + ast.subexpression([ast.field('foo'), + ast.field('bar')])) + + def test_multiple_dots(self): + parsed = self.parser.parse('foo.bar.baz') + self.assertEqual( + parsed.search({'foo': {'bar': {'baz': 'correct'}}}), 'correct') + + def test_index(self): + parsed = self.parser.parse('foo[1]') + self.assertEqual( + parsed.search({'foo': ['zero', 'one', 'two']}), + 'one') + + def test_quoted_subexpression(self): + self.assert_parsed_ast('"foo"."bar"', + ast.subexpression([ + ast.field('foo'), + ast.field('bar')])) + + def test_wildcard(self): + parsed = self.parser.parse('foo[*]') + self.assertEqual( + parsed.search({'foo': ['zero', 'one', 'two']}), + ['zero', 'one', 'two']) + + def test_wildcard_with_children(self): + parsed = self.parser.parse('foo[*].bar') + self.assertEqual( + parsed.search({'foo': [{'bar': 'one'}, {'bar': 'two'}]}), + ['one', 'two']) + + def test_or_expression(self): + parsed = self.parser.parse('foo || bar') + self.assertEqual(parsed.search({'foo': 'foo'}), 'foo') + self.assertEqual(parsed.search({'bar': 'bar'}), 'bar') + self.assertEqual(parsed.search({'foo': 'foo', 'bar': 'bar'}), 'foo') + self.assertEqual(parsed.search({'bad': 'bad'}), None) + + def test_complex_or_expression(self): + parsed = self.parser.parse('foo.foo || foo.bar') + self.assertEqual(parsed.search({'foo': {'foo': 'foo'}}), 'foo') + self.assertEqual(parsed.search({'foo': {'bar': 'bar'}}), 'bar') + self.assertEqual(parsed.search({'foo': {'baz': 'baz'}}), None) + + def test_or_repr(self): + self.assert_parsed_ast('foo || bar', ast.or_expression(ast.field('foo'), + ast.field('bar'))) + + def test_unicode_literals_escaped(self): + self.assert_parsed_ast(r'`"\u2713"`', ast.literal(u'\u2713')) + + def test_multiselect(self): + parsed = self.parser.parse('foo.{bar: bar,baz: baz}') + self.assertEqual( + parsed.search({'foo': {'bar': 'bar', 'baz': 'baz', 'qux': 'qux'}}), + {'bar': 'bar', 'baz': 'baz'}) + + def test_multiselect_subexpressions(self): + parsed = self.parser.parse('foo.{"bar.baz": bar.baz, qux: qux}') + self.assertEqual( + parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, 'qux': 'qux'}}), + {'bar.baz': 'CORRECT', 'qux': 'qux'}) + + def test_multiselect_with_all_quoted_keys(self): + parsed = self.parser.parse('foo.{"bar": bar.baz, "qux": qux}') + result = parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, 'qux': 'qux'}}) + self.assertEqual(result, {"bar": "CORRECT", "qux": "qux"}) + + def test_function_call_with_and_statement(self): + self.assert_parsed_ast( + 'f(@ && @)', + {'children': [{'children': [{'children': [], 'type': 'current'}, + {'children': [], 'type': 'current'}], + 'type': 'and_expression'}], + 'type': 'function_expression', + 'value': 'f'}) + + +class TestErrorMessages(unittest.TestCase): + + def setUp(self): + self.parser = parser.Parser() + + def assert_error_message(self, expression, error_message, + exception=exceptions.ParseError): + try: + self.parser.parse(expression) + except exception as e: + self.assertEqual(error_message, str(e)) + return + except Exception as e: + self.fail( + "Unexpected error raised (%s: %s) for bad expression: %s" % + (e.__class__.__name__, e, expression)) + else: + self.fail( + "ParseError not raised for bad expression: %s" % expression) + + def test_bad_parse(self): + with self.assertRaises(exceptions.ParseError): + self.parser.parse('foo]baz') + + def test_bad_parse_error_message(self): + error_message = ( + 'Unexpected token: ]: Parse error at column 3, ' + 'token "]" (RBRACKET), for expression:\n' + '"foo]baz"\n' + ' ^') + self.assert_error_message('foo]baz', error_message) + + def test_bad_parse_error_message_with_multiselect(self): + error_message = ( + 'Invalid jmespath expression: Incomplete expression:\n' + '"foo.{bar: baz,bar: bar"\n' + ' ^') + self.assert_error_message('foo.{bar: baz,bar: bar', error_message) + + def test_incomplete_expression_with_missing_paren(self): + error_message = ( + 'Invalid jmespath expression: Incomplete expression:\n' + '"length(@,"\n' + ' ^') + self.assert_error_message('length(@,', error_message) + + def test_bad_lexer_values(self): + error_message = ( + 'Bad jmespath expression: ' + 'Unclosed " delimiter:\n' + 'foo."bar\n' + ' ^') + self.assert_error_message('foo."bar', error_message, + exception=exceptions.LexerError) + + def test_bad_unicode_string(self): + # This error message is straight from the JSON parser + # and pypy has a slightly different error message, + # so we're not using assert_error_message. + error_message = re.compile( + r'Bad jmespath expression: ' + r'Invalid \\uXXXX escape.*\\uAZ12', re.DOTALL) + with self.assertRaisesRegexp(exceptions.LexerError, error_message): + self.parser.parse(r'"\uAZ12"') + + +class TestParserWildcards(unittest.TestCase): + def setUp(self): + self.parser = parser.Parser() + self.data = { + 'foo': [ + {'bar': [{'baz': 'one'}, {'baz': 'two'}]}, + {'bar': [{'baz': 'three'}, {'baz': 'four'}, {'baz': 'five'}]}, + ] + } + + def test_multiple_index_wildcards(self): + parsed = self.parser.parse('foo[*].bar[*].baz') + self.assertEqual(parsed.search(self.data), + [['one', 'two'], ['three', 'four', 'five']]) + + def test_wildcard_mix_with_indices(self): + parsed = self.parser.parse('foo[*].bar[0].baz') + self.assertEqual(parsed.search(self.data), + ['one', 'three']) + + def test_wildcard_mix_last(self): + parsed = self.parser.parse('foo[0].bar[*].baz') + self.assertEqual(parsed.search(self.data), + ['one', 'two']) + + def test_indices_out_of_bounds(self): + parsed = self.parser.parse('foo[*].bar[2].baz') + self.assertEqual(parsed.search(self.data), + ['five']) + + def test_root_indices(self): + parsed = self.parser.parse('[0]') + self.assertEqual(parsed.search(['one', 'two']), 'one') + + def test_root_wildcard(self): + parsed = self.parser.parse('*.foo') + data = {'top1': {'foo': 'bar'}, 'top2': {'foo': 'baz'}, + 'top3': {'notfoo': 'notfoo'}} + # Sorted is being used because the order of the keys are not + # required to be in any specific order. + self.assertEqual(sorted(parsed.search(data)), sorted(['bar', 'baz'])) + self.assertEqual(sorted(self.parser.parse('*.notfoo').search(data)), + sorted(['notfoo'])) + + def test_only_wildcard(self): + parsed = self.parser.parse('*') + data = {'foo': 'a', 'bar': 'b', 'baz': 'c'} + self.assertEqual(sorted(parsed.search(data)), sorted(['a', 'b', 'c'])) + + def test_escape_sequences(self): + self.assertEqual(self.parser.parse(r'"foo\tbar"').search( + {'foo\tbar': 'baz'}), 'baz') + self.assertEqual(self.parser.parse(r'"foo\nbar"').search( + {'foo\nbar': 'baz'}), 'baz') + self.assertEqual(self.parser.parse(r'"foo\bbar"').search( + {'foo\bbar': 'baz'}), 'baz') + self.assertEqual(self.parser.parse(r'"foo\fbar"').search( + {'foo\fbar': 'baz'}), 'baz') + self.assertEqual(self.parser.parse(r'"foo\rbar"').search( + {'foo\rbar': 'baz'}), 'baz') + + def test_consecutive_escape_sequences(self): + parsed = self.parser.parse(r'"foo\\nbar"') + self.assertEqual(parsed.search({'foo\\nbar': 'baz'}), 'baz') + + parsed = self.parser.parse(r'"foo\n\t\rbar"') + self.assertEqual(parsed.search({'foo\n\t\rbar': 'baz'}), 'baz') + + def test_escape_sequence_at_end_of_string_not_allowed(self): + with self.assertRaises(ValueError): + self.parser.parse('foobar\\') + + def test_wildcard_with_multiselect(self): + parsed = self.parser.parse('foo.*.{a: a, b: b}') + data = { + 'foo': { + 'one': { + 'a': {'c': 'CORRECT', 'd': 'other'}, + 'b': {'c': 'ALSOCORRECT', 'd': 'other'}, + }, + 'two': { + 'a': {'c': 'CORRECT', 'd': 'other'}, + 'c': {'c': 'WRONG', 'd': 'other'}, + }, + } + } + match = parsed.search(data) + self.assertEqual(len(match), 2) + self.assertIn('a', match[0]) + self.assertIn('b', match[0]) + self.assertIn('a', match[1]) + self.assertIn('b', match[1]) + + +class TestMergedLists(unittest.TestCase): + def setUp(self): + self.parser = parser.Parser() + self.data = { + "foo": [ + [["one", "two"], ["three", "four"]], + [["five", "six"], ["seven", "eight"]], + [["nine"], ["ten"]] + ] + } + + def test_merge_with_indices(self): + parsed = self.parser.parse('foo[][0]') + match = parsed.search(self.data) + self.assertEqual(match, ["one", "three", "five", "seven", + "nine", "ten"]) + + def test_trailing_merged_operator(self): + parsed = self.parser.parse('foo[]') + match = parsed.search(self.data) + self.assertEqual( + match, + [["one", "two"], ["three", "four"], + ["five", "six"], ["seven", "eight"], + ["nine"], ["ten"]]) + + +class TestParserCaching(unittest.TestCase): + def test_compile_lots_of_expressions(self): + # We have to be careful here because this is an implementation detail + # that should be abstracted from the user, but we need to make sure we + # exercise the code and that it doesn't blow up. + p = parser.Parser() + compiled = [] + compiled2 = [] + for i in range(parser.Parser._MAX_SIZE + 1): + compiled.append(p.parse('foo%s' % i)) + # Rerun the test and half of these entries should be from the + # cache but they should still be equal to compiled. + for i in range(parser.Parser._MAX_SIZE + 1): + compiled2.append(p.parse('foo%s' % i)) + self.assertEqual(len(compiled), len(compiled2)) + self.assertEqual( + [expr.parsed for expr in compiled], + [expr.parsed for expr in compiled2]) + + def test_cache_purge(self): + p = parser.Parser() + first = p.parse('foo') + cached = p.parse('foo') + p.purge() + second = p.parse('foo') + self.assertEqual(first.parsed, + second.parsed) + self.assertEqual(first.parsed, + cached.parsed) + + +class TestParserAddsExpressionAttribute(unittest.TestCase): + def test_expression_available_from_parser(self): + p = parser.Parser() + parsed = p.parse('foo.bar') + self.assertEqual(parsed.expression, 'foo.bar') + + +class TestParsedResultAddsOptions(unittest.TestCase): + def test_can_have_ordered_dict(self): + p = parser.Parser() + parsed = p.parse('{a: a, b: b, c: c}') + options = visitor.Options(dict_cls=OrderedDict) + result = parsed.search( + {"c": "c", "b": "b", "a": "a"}, options=options) + # The order should be 'a', 'b' because we're using an + # OrderedDict + self.assertEqual(list(result), ['a', 'b', 'c']) + + +class TestRenderGraphvizFile(unittest.TestCase): + def test_dot_file_rendered(self): + p = parser.Parser() + result = p.parse('foo') + dot_contents = result._render_dot_file() + self.assertEqual(dot_contents, + 'digraph AST {\nfield1 [label="field(foo)"]\n}') + + def test_dot_file_subexpr(self): + p = parser.Parser() + result = p.parse('foo.bar') + dot_contents = result._render_dot_file() + self.assertEqual( + dot_contents, + 'digraph AST {\n' + 'subexpression1 [label="subexpression()"]\n' + ' subexpression1 -> field2\n' + 'field2 [label="field(foo)"]\n' + ' subexpression1 -> field3\n' + 'field3 [label="field(bar)"]\n}') + + +if __name__ == '__main__': + unittest.main() diff --git a/contrib/python/jmespath/tests/ya.make b/contrib/python/jmespath/tests/ya.make index c865997f16..46ad0e1bd9 100644 --- a/contrib/python/jmespath/tests/ya.make +++ b/contrib/python/jmespath/tests/ya.make @@ -1,17 +1,17 @@ -PY23_TEST() - -OWNER(g:python-contrib) - -PEERDIR( - contrib/python/jmespath -) - -TEST_SRCS( - __init__.py - test_compliance.py - test_parser.py -) - -NO_LINT() - -END() +PY23_TEST() + +OWNER(g:python-contrib) + +PEERDIR( + contrib/python/jmespath +) + +TEST_SRCS( + __init__.py + test_compliance.py + test_parser.py +) + +NO_LINT() + +END() diff --git a/contrib/python/jmespath/ya.make b/contrib/python/jmespath/ya.make index 1ffbd236bc..9943d79f1c 100644 --- a/contrib/python/jmespath/ya.make +++ b/contrib/python/jmespath/ya.make @@ -1,33 +1,33 @@ PY23_LIBRARY() -OWNER(g:python-contrib) +OWNER(g:python-contrib) -VERSION(0.10.0) - -LICENSE(MIT) - -NO_LINT() +VERSION(0.10.0) +LICENSE(MIT) + +NO_LINT() + PY_SRCS( TOP_LEVEL - jmespath/__init__.py - jmespath/ast.py - jmespath/compat.py + jmespath/__init__.py + jmespath/ast.py + jmespath/compat.py jmespath/exceptions.py jmespath/functions.py jmespath/lexer.py jmespath/parser.py - jmespath/visitor.py -) - -RESOURCE_FILES( - PREFIX contrib/python/jmespath/ - .dist-info/METADATA - .dist-info/top_level.txt + jmespath/visitor.py ) +RESOURCE_FILES( + PREFIX contrib/python/jmespath/ + .dist-info/METADATA + .dist-info/top_level.txt +) + END() - -RECURSE_FOR_TESTS( - tests -) + +RECURSE_FOR_TESTS( + tests +) |