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 - [email protected]
+
+- 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: [email protected]
 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: [email protected]
 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='[email protected]',
-    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()


Reply via email to