commit python-jmespath for openSUSE:Factory
Hello community, here is the log from the commit of package python-jmespath for openSUSE:Factory checked in at 2015-05-28 09:57:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jmespath (Old) and /work/SRC/openSUSE:Factory/.python-jmespath.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-jmespath" Changes: -------- --- /work/SRC/openSUSE:Factory/python-jmespath/python-jmespath.changes 2015-04-27 13:05:29.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-jmespath.new/python-jmespath.changes 2015-05-28 09:57:57.000000000 +0200 @@ -1,0 +2,12 @@ +Wed May 27 17:04:07 UTC 2015 - rjschwei@suse.com + +- Update to version 0.7.1: + * Rename ``bin/jp`` to ``bin/jp.py`` + * Fix issue with precedence when parsing wildcard + projections + * Remove ordereddict and simplejson as py2.6 dependencies. + These were never actually used in the jmespath code base, + only in the unit tests. Unittests requirements are handled + via requirements26.txt. + +------------------------------------------------------------------- Old: ---- jmespath-0.7.0.tar.gz New: ---- jmespath-0.7.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jmespath.spec ++++++ --- /var/tmp/diff_new_pack.6XGgDn/_old 2015-05-28 09:57:57.000000000 +0200 +++ /var/tmp/diff_new_pack.6XGgDn/_new 2015-05-28 09:57:57.000000000 +0200 @@ -19,7 +19,7 @@ %define baseName jmespath Name: python-jmespath -Version: 0.7.0 +Version: 0.7.1 Release: 0 Summary: Extract elements from JSON document License: MIT @@ -78,6 +78,9 @@ %install python setup.py install --prefix=%{_prefix} --root=%{buildroot} --install-scripts=%{_bindir} +pushd %{buildroot}/%{_bindir} +ln -s jp.py jp +popd %files %defattr(-,root,root,-) @@ -85,6 +88,7 @@ %dir %{python_sitelib}/jmespath %dir %{python_sitelib}/%{baseName}-%{version}-py%{py_ver}.egg-info %{_bindir}/jp +%{_bindir}/jp.py %{python_sitelib}/jmespath/* %{python_sitelib}/*egg-info/* ++++++ jmespath-0.7.0.tar.gz -> jmespath-0.7.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/PKG-INFO new/jmespath-0.7.1/PKG-INFO --- old/jmespath-0.7.0/PKG-INFO 2015-04-21 08:34:36.000000000 +0200 +++ new/jmespath-0.7.1/PKG-INFO 2015-04-27 19:29:56.000000000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 1.1 Name: jmespath -Version: 0.7.0 +Version: 0.7.1 Summary: JSON Matching Expressions -Home-page: https://github.com/boto/jmespath +Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie Author-email: js@jamesls.com License: UNKNOWN @@ -121,3 +121,4 @@ Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/bin/jp new/jmespath-0.7.1/bin/jp --- old/jmespath-0.7.0/bin/jp 2015-04-18 20:54:14.000000000 +0200 +++ new/jmespath-0.7.1/bin/jp 1970-01-01 01:00:00.000000000 +0100 @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -import sys -import json -import argparse - -import jmespath -from jmespath import exceptions -from jmespath.compat import OrderedDict - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('expression') - parser.add_argument('-o', '--ordered', action='store_true', - help='Preserve the order of hash keys, which ' - 'are normally unordered.') - parser.add_argument('-f', '--filename', - help=('The filename containing the input data. ' - 'If a filename is not given then data is ' - 'read from stdin.')) - parser.add_argument('--ast', action='store_true', - help=('Pretty print the AST, do not search the data.')) - args = parser.parse_args() - expression = args.expression - if args.ast: - # Only print the AST - expression = jmespath.compile(args.expression) - sys.stdout.write(str(expression)) - sys.stdout.write('\n') - return 0 - if args.filename: - with open(args.filename, 'r') as f: - data = json.load(f) - else: - data = sys.stdin.read() - if args.ordered: - data = json.loads(data, object_pairs_hook=OrderedDict) - else: - data = json.loads(data) - try: - sys.stdout.write(json.dumps( - jmespath.search(expression, data), indent=4)) - sys.stdout.write('\n') - except exceptions.ArityError as e: - sys.stderr.write("invalid-arity: %s\n" % e) - return 1 - except exceptions.JMESPathTypeError as e: - sys.stderr.write("invalid-type: %s\n" % e) - return 1 - except exceptions.UnknownFunctionError as e: - sys.stderr.write("unknown-function: %s\n" % e) - return 1 - except exceptions.ParseError as e: - sys.stderr.write("syntax-error: %s\n" % e) - return 1 - - -if __name__ == '__main__': - sys.exit(main()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/bin/jp.py new/jmespath-0.7.1/bin/jp.py --- old/jmespath-0.7.0/bin/jp.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jmespath-0.7.1/bin/jp.py 2015-04-27 19:27:03.000000000 +0200 @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import sys +import json +import argparse + +import jmespath +from jmespath import exceptions + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('expression') + parser.add_argument('-f', '--filename', + help=('The filename containing the input data. ' + 'If a filename is not given then data is ' + 'read from stdin.')) + parser.add_argument('--ast', action='store_true', + help=('Pretty print the AST, do not search the data.')) + args = parser.parse_args() + expression = args.expression + if args.ast: + # Only print the AST + expression = jmespath.compile(args.expression) + sys.stdout.write(str(expression)) + sys.stdout.write('\n') + return 0 + if args.filename: + with open(args.filename, 'r') as f: + data = json.load(f) + else: + data = sys.stdin.read() + data = json.loads(data) + try: + sys.stdout.write(json.dumps( + jmespath.search(expression, data), indent=4)) + sys.stdout.write('\n') + except exceptions.ArityError as e: + sys.stderr.write("invalid-arity: %s\n" % e) + return 1 + except exceptions.JMESPathTypeError as e: + sys.stderr.write("invalid-type: %s\n" % e) + return 1 + except exceptions.UnknownFunctionError as e: + sys.stderr.write("unknown-function: %s\n" % e) + return 1 + except exceptions.ParseError as e: + sys.stderr.write("syntax-error: %s\n" % e) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath/__init__.py new/jmespath-0.7.1/jmespath/__init__.py --- old/jmespath-0.7.0/jmespath/__init__.py 2015-04-21 08:29:17.000000000 +0200 +++ new/jmespath-0.7.1/jmespath/__init__.py 2015-04-27 19:28:36.000000000 +0200 @@ -1,6 +1,6 @@ from jmespath import parser -__version__ = '0.7.0' +__version__ = '0.7.1' def compile(expression): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath/compat.py new/jmespath-0.7.1/jmespath/compat.py --- old/jmespath-0.7.0/jmespath/compat.py 2015-03-20 03:58:23.000000000 +0100 +++ new/jmespath-0.7.1/jmespath/compat.py 2015-04-27 19:27:03.000000000 +0200 @@ -54,11 +54,3 @@ for name, method in inspect.getmembers(cls, predicate=inspect.isfunction): yield name, method - - -if sys.version_info[:2] == (2, 6): - from ordereddict import OrderedDict - import simplejson as json -else: - from collections import OrderedDict - import json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath/functions.py new/jmespath-0.7.1/jmespath/functions.py --- old/jmespath-0.7.0/jmespath/functions.py 2015-04-18 20:54:14.000000000 +0200 +++ new/jmespath-0.7.1/jmespath/functions.py 2015-04-27 19:27:03.000000000 +0200 @@ -1,4 +1,3 @@ -import inspect import math import json import weakref diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath/parser.py new/jmespath-0.7.1/jmespath/parser.py --- old/jmespath-0.7.0/jmespath/parser.py 2015-04-21 08:28:55.000000000 +0200 +++ new/jmespath-0.7.1/jmespath/parser.py 2015-04-27 19:27:03.000000000 +0200 @@ -57,7 +57,7 @@ 'or': 5, 'flatten': 6, 'star': 20, - 'filter': 20, + 'filter': 21, 'dot': 40, 'lbrace': 50, 'lbracket': 55, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath.egg-info/PKG-INFO new/jmespath-0.7.1/jmespath.egg-info/PKG-INFO --- old/jmespath-0.7.0/jmespath.egg-info/PKG-INFO 2015-04-21 08:34:35.000000000 +0200 +++ new/jmespath-0.7.1/jmespath.egg-info/PKG-INFO 2015-04-27 19:29:56.000000000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 1.1 Name: jmespath -Version: 0.7.0 +Version: 0.7.1 Summary: JSON Matching Expressions -Home-page: https://github.com/boto/jmespath +Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie Author-email: js@jamesls.com License: UNKNOWN @@ -121,3 +121,4 @@ Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath.egg-info/SOURCES.txt new/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt --- old/jmespath-0.7.0/jmespath.egg-info/SOURCES.txt 2015-04-21 08:34:36.000000000 +0200 +++ new/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt 2015-04-27 19:29:56.000000000 +0200 @@ -3,7 +3,7 @@ README.rst setup.cfg setup.py -bin/jp +bin/jp.py jmespath/__init__.py jmespath/ast.py jmespath/compat.py @@ -15,5 +15,7 @@ jmespath.egg-info/PKG-INFO jmespath.egg-info/SOURCES.txt jmespath.egg-info/dependency_links.txt -jmespath.egg-info/pbr.json -jmespath.egg-info/top_level.txt \ No newline at end of file +jmespath.egg-info/top_level.txt +tests/__init__.py +tests/test_compliance.py +tests/test_parser.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/jmespath.egg-info/pbr.json new/jmespath-0.7.1/jmespath.egg-info/pbr.json --- old/jmespath-0.7.0/jmespath.egg-info/pbr.json 2015-04-21 08:34:36.000000000 +0200 +++ new/jmespath-0.7.1/jmespath.egg-info/pbr.json 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -{"is_release": true, "git_version": "0466cc1"} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/setup.py new/jmespath-0.7.1/setup.py --- old/jmespath-0.7.0/setup.py 2015-04-21 08:29:17.000000000 +0200 +++ new/jmespath-0.7.1/setup.py 2015-04-27 19:28:36.000000000 +0200 @@ -7,33 +7,16 @@ from setuptools import setup, find_packages -requires = [] - - -if sys.version_info[:2] == (2, 6): - # For python2.6 we have a few other dependencies. - # First we need an ordered dictionary so we use the - # 2.6 backport. - requires.append('ordereddict==1.1') - # Then we need simplejson. This is because we need - # a json version that allows us to specify we want to - # use an ordereddict instead of a normal dict for the - # JSON objects. The 2.7 json module has this. For 2.6 - # we need simplejson. - requires.append('simplejson==3.3.0') - - setup( name='jmespath', - version='0.7.0', + version='0.7.1', description='JSON Matching Expressions', long_description=io.open('README.rst', encoding='utf-8').read(), author='James Saryerwinnie', author_email='js@jamesls.com', - url='https://github.com/boto/jmespath', - scripts=['bin/jp'], + url='https://github.com/jmespath/jmespath.py', + scripts=['bin/jp.py'], packages=find_packages(exclude=['tests']), - install_requires=requires, classifiers=( 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -44,5 +27,6 @@ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', ), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/tests/__init__.py new/jmespath-0.7.1/tests/__init__.py --- old/jmespath-0.7.0/tests/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jmespath-0.7.1/tests/__init__.py 2014-04-23 21:00:09.000000000 +0200 @@ -0,0 +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(")") + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/tests/test_compliance.py new/jmespath-0.7.1/tests/test_compliance.py --- old/jmespath-0.7.0/tests/test_compliance.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jmespath-0.7.1/tests/test_compliance.py 2015-04-27 19:23:22.000000000 +0200 @@ -0,0 +1,96 @@ +import os +from pprint import pformat +from tests import OrderedDict +from tests import json + +from nose.tools import assert_equal + +import jmespath +from jmespath.visitor import TreeInterpreter + + +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() +TreeInterpreter.MAP_TYPE = OrderedDict + + +def test_compliance(): + for full_path in _walk_files(): + if full_path.endswith('.json'): + for given, expression, result, error in _load_cases(full_path): + if error is NOT_SPECIFIED and result is not NOT_SPECIFIED: + yield (_test_expression, given, expression, + result, os.path.basename(full_path)) + elif result is NOT_SPECIFIED and error is not NOT_SPECIFIED: + yield (_test_error_expression, given, expression, + error, os.path.basename(full_path)) + else: + parts = (given, expression, result, error) + raise RuntimeError("Invalid test description: %s" % parts) + + +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']: + yield (given, case['expression'], + case.get('result', NOT_SPECIFIED), + case.get('error', NOT_SPECIFIED)) + + +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) + 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, parsed, + json.dumps(given, indent=4))) + error_msg = error_msg.replace(r'\n', '\n') + assert_equal(actual, 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 as e: + # Test passes, it raised a parse error as expected. + pass + 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, parsed)) + error_msg = error_msg.replace(r'\n', '\n') + raise AssertionError(error_msg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.0/tests/test_parser.py new/jmespath-0.7.1/tests/test_parser.py --- old/jmespath-0.7.0/tests/test_parser.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jmespath-0.7.1/tests/test_parser.py 2015-04-27 19:23:22.000000000 +0200 @@ -0,0 +1,352 @@ +#!/usr/bin/env python + +import re +from tests import unittest + +from jmespath import parser +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"}) + + +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: ' + 'Starting quote is missing the ending quote:\n' + 'foo."bar\n' + ' ^') + self.assert_error_message('foo."bar', error_message, + exception=exceptions.LexerError) + + def test_bad_lexer_literal_value_with_json_object(self): + error_message = ('Bad jmespath expression: ' + 'Bad token `{{}`:\n`{{}`\n^') + self.assert_error_message('`{{}`', 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 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()
participants (1)
-
root@hilbert.suse.de