Hello community, here is the log from the commit of package python-jmespath for openSUSE:Factory checked in at 2016-03-29 14:50:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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-05-28 09:57:57.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-jmespath.new/python-jmespath.changes 2016-03-29 14:50:33.000000000 +0200 @@ -1,0 +2,33 @@ +Mon Feb 1 11:24:14 UTC 2016 - toddrme2...@gmail.com + +- Update to version 0.9.0 + * Add support for new lines with tokens in an expression + * Add support for `JEP 9 <http://jmespath.org/proposals/improved-filters.html>`__, + which introduces "and" expressions, "unary" expressions, "not" expressions, + and "paren" expressions + * Fix issue with hardcoded path in ``jp.py`` executable + (`issue 90 <https://github.com/jmespath/jmespath.py/issues/90>`__, + `issue 88 <https://github.com/jmespath/jmespath.py/issues/88>`__, + `issue 82 <https://github.com/jmespath/jmespath.py/issues/82>`__) +- Update to version 0.8.0 + * Improve lexing performance (`issue 84 <https://github.com/jmespath/jmespath.py/pull/84>`__) + * Fix parsing error for multiselect lists (`issue 86 <https://github.com/jmespath/jmespath.py/issues/86>`__) + * Fix issue with escaping single quotes in literal strings (`issue 85 <https://github.com/jmespath/jmespath.py/issues/85>`__) + * Add support for providing your own dict cls to support + ordered dictionaries (`issue 94 <https://github.com/jmespath/jmespath.py/pull/94>`__) + * Add map() function (`issue 95 <https://github.com/jmespath/jmespath.py/pull/95>`__) + +------------------------------------------------------------------- +Wed Jan 20 19:42:39 UTC 2016 - rjsch...@suse.com + +- Fix build issues for SLE 11 + + More test dependencies required + +------------------------------------------------------------------- +Wed Jan 20 19:08:32 UTC 2016 - rjsch...@suse.com + +- Manage the jp executable with update-alternatives now that + python3-jmespath exists +- spec file improvements + +------------------------------------------------------------------- Old: ---- jmespath-0.7.1.tar.gz New: ---- jmespath-0.9.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jmespath.spec ++++++ --- /var/tmp/diff_new_pack.p1ENnt/_old 2016-03-29 14:50:34.000000000 +0200 +++ /var/tmp/diff_new_pack.p1ENnt/_new 2016-03-29 14:50:34.000000000 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-jmespath # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,19 +19,27 @@ %define baseName jmespath Name: python-jmespath -Version: 0.7.1 +Version: 0.9.0 Release: 0 Summary: Extract elements from JSON document License: MIT Group: Development/Languages/Python Url: https://github.com/boto/jmespath Source0: https://pypi.python.org/packages/source/j/%{baseName}/%{baseName}-%{version}.tar.gz -Requires: python +Requires: python-base Requires: python-ply >= 3.4 -BuildRequires: python +Requires(post): update-alternatives +Requires(postun): update-alternatives BuildRequires: python-devel -BuildRequires: python-ply +BuildRequires: python-ply >= 3.4 BuildRequires: python-setuptools +# For testing +BuildRequires: python-nose +%if 0%{?suse_version} && 0%{?suse_version} <= 1110 +BuildRequires: python-ordereddict +%endif +BuildRequires: python-simplejson +BuildRequires: python-unittest2 BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} @@ -79,17 +87,25 @@ %install python setup.py install --prefix=%{_prefix} --root=%{buildroot} --install-scripts=%{_bindir} pushd %{buildroot}/%{_bindir} -ln -s jp.py jp +mv jp.py jp-%{py_ver} popd +%check +nosetests tests + +%post +"%_sbindir/update-alternatives" --install %{_bindir}/jp jp %{_bindir}/jp-%{py_ver} 30 + +%postun +if [ $1 -eq 0 ] ; then + "%_sbindir/update-alternatives" --remove jp %{_bindir}/jp-%{py_ver} +fi + %files %defattr(-,root,root,-) %doc LICENSE.txt README.rst -%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/* +%{_bindir}/jp-%{py_ver} +%{python_sitelib}/jmespath/ +%{python_sitelib}/%{baseName}-%{version}-py%{py_ver}.egg-info/ %changelog ++++++ jmespath-0.7.1.tar.gz -> jmespath-0.9.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/PKG-INFO new/jmespath-0.9.0/PKG-INFO --- old/jmespath-0.7.1/PKG-INFO 2015-04-27 19:29:56.000000000 +0200 +++ new/jmespath-0.9.0/PKG-INFO 2015-10-01 06:12:12.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: jmespath -Version: 0.7.1 +Version: 0.9.0 Summary: JSON Matching Expressions Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie @@ -77,6 +77,26 @@ 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:: + + >>> 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('{a: a, b: b}, + ... mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + Specification ============= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/README.rst new/jmespath-0.9.0/README.rst --- old/jmespath-0.7.1/README.rst 2015-04-27 19:23:22.000000000 +0200 +++ new/jmespath-0.9.0/README.rst 2015-09-30 06:37:03.000000000 +0200 @@ -69,6 +69,26 @@ 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:: + + >>> 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('{a: a, b: b}, + ... mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + Specification ============= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/bin/jp.py new/jmespath-0.9.0/bin/jp.py --- old/jmespath-0.7.1/bin/jp.py 2015-04-27 19:27:03.000000000 +0200 +++ new/jmespath-0.9.0/bin/jp.py 2015-09-23 07:05:13.000000000 +0200 @@ -3,6 +3,7 @@ import sys import json import argparse +from pprint import pformat import jmespath from jmespath import exceptions @@ -22,7 +23,7 @@ if args.ast: # Only print the AST expression = jmespath.compile(args.expression) - sys.stdout.write(str(expression)) + sys.stdout.write(pformat(expression.parsed)) sys.stdout.write('\n') return 0 if args.filename: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/__init__.py new/jmespath-0.9.0/jmespath/__init__.py --- old/jmespath-0.7.1/jmespath/__init__.py 2015-04-27 19:28:36.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/__init__.py 2015-10-01 06:11:26.000000000 +0200 @@ -1,11 +1,12 @@ from jmespath import parser +from jmespath.visitor import Options -__version__ = '0.7.1' +__version__ = '0.9.0' def compile(expression): return parser.Parser().parse(expression) -def search(expression, data): - return parser.Parser().parse(expression).search(data) +def search(expression, data, options=None): + return parser.Parser().parse(expression).search(data, options=options) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/ast.py new/jmespath-0.9.0/jmespath/ast.py --- old/jmespath-0.7.1/jmespath/ast.py 2015-04-14 01:16:58.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/ast.py 2015-10-01 06:11:19.000000000 +0200 @@ -62,6 +62,14 @@ return {"type": "or_expression", "children": [left, right]} +def and_expression(left, right): + return {"type": "and_expression", "children": [left, right]} + + +def not_expression(expr): + return {"type": "not_expression", "children": [expr]} + + def pipe(left, right): return {'type': 'pipe', 'children': [left, right]} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/functions.py new/jmespath-0.9.0/jmespath/functions.py --- old/jmespath-0.7.1/jmespath/functions.py 2015-04-27 19:27:03.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/functions.py 2015-09-30 06:37:03.000000000 +0200 @@ -251,6 +251,13 @@ def _func_join(self, separator, array): return separator.join(array) + @builtin_function({'types': ['expref']}, {'types': ['array']}) + def _func_map(self, expref, arg): + result = [] + for element in arg: + result.append(self.interpreter.visit(expref.expression, element)) + return result + @builtin_function({"types": ['array-number', 'array-string']}) def _func_max(self, arg): if arg: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/lexer.py new/jmespath-0.9.0/jmespath/lexer.py --- old/jmespath-0.7.1/jmespath/lexer.py 2015-04-27 19:23:22.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/lexer.py 2015-10-01 06:11:19.000000000 +0200 @@ -1,4 +1,4 @@ -import re +import string import warnings from json import loads @@ -6,143 +6,172 @@ class Lexer(object): - TOKENS = ( - r'(?P<number>-?\d+)|' - r'(?P<unquoted_identifier>([a-zA-Z_][a-zA-Z_0-9]*))|' - r'(?P<quoted_identifier>("(?:\\\\|\\"|[^"])*"))|' - r'(?P<string_literal>(\'(?:\\\\|\\\'|[^\'])*\'))|' - r'(?P<literal>(`(?:\\\\|\\`|[^`])*`))|' - r'(?P<filter>\[\?)|' - r'(?P<or>\|\|)|' - r'(?P<pipe>\|)|' - r'(?P<ne>!=)|' - r'(?P<rbrace>\})|' - r'(?P<eq>==)|' - r'(?P<dot>\.)|' - r'(?P<star>\*)|' - r'(?P<gte>>=)|' - r'(?P<lparen>\()|' - r'(?P<lbrace>\{)|' - r'(?P<lte><=)|' - r'(?P<flatten>\[\])|' - r'(?P<rbracket>\])|' - r'(?P<lbracket>\[)|' - r'(?P<rparen>\))|' - r'(?P<comma>,)|' - r'(?P<colon>:)|' - r'(?P<lt><)|' - r'(?P<expref>&)|' - r'(?P<gt>>)|' - r'(?P<current>@)|' - r'(?P<skip>[ \t]+)' - ) - - def __init__(self): - self.master_regex = re.compile(self.TOKENS) + START_IDENTIFIER = set(string.ascii_letters + '_') + VALID_IDENTIFIER = set(string.ascii_letters + string.digits + '_') + START_NUMBER = set(string.digits + '-') + VALID_NUMBER = set(string.digits) + WHITESPACE = set(" \t\n\r") + SIMPLE_TOKENS = { + '.': 'dot', + '*': 'star', + ']': 'rbracket', + ',': 'comma', + ':': 'colon', + '@': 'current', + '(': 'lparen', + ')': 'rparen', + '{': 'lbrace', + '}': 'rbrace', + } def tokenize(self, expression): + self._initialize_for_expression(expression) + while self._current is not None: + if self._current in self.SIMPLE_TOKENS: + yield {'type': self.SIMPLE_TOKENS[self._current], + 'value': self._current, + 'start': self._position, 'end': self._position + 1} + self._next() + elif self._current in self.START_IDENTIFIER: + start = self._position + buff = self._current + while self._next() in self.VALID_IDENTIFIER: + buff += self._current + yield {'type': 'unquoted_identifier', 'value': buff, + 'start': start, 'end': start + len(buff)} + elif self._current in self.WHITESPACE: + self._next() + elif self._current == '[': + start = self._position + next_char = self._next() + if next_char == ']': + self._next() + yield {'type': 'flatten', 'value': '[]', + 'start': start, 'end': start + 2} + elif next_char == '?': + self._next() + yield {'type': 'filter', 'value': '[?', + 'start': start, 'end': start + 2} + else: + yield {'type': 'lbracket', 'value': '[', + 'start': start, 'end': start + 1} + elif self._current == "'": + yield self._consume_raw_string_literal() + elif self._current == '|': + yield self._match_or_else('|', 'or', 'pipe') + elif self._current == '&': + yield self._match_or_else('&', 'and', 'expref') + elif self._current == '`': + yield self._consume_literal() + elif self._current in self.START_NUMBER: + start = self._position + buff = self._current + while self._next() in self.VALID_NUMBER: + buff += self._current + yield {'type': 'number', 'value': int(buff), + 'start': start, 'end': start + len(buff)} + elif self._current == '"': + yield self._consume_quoted_identifier() + elif self._current == '<': + yield self._match_or_else('=', 'lte', 'lt') + elif self._current == '>': + yield self._match_or_else('=', 'gte', 'gt') + elif self._current == '!': + yield self._match_or_else('=', 'ne', 'not') + elif self._current == '=': + yield self._match_or_else('=', 'eq', 'unknown') + else: + raise LexerError(lexer_position=self._position, + lexer_value=self._current, + message="Unknown token %s" % self._current) + yield {'type': 'eof', 'value': '', + 'start': self._length, 'end': self._length} + + def _initialize_for_expression(self, expression): if not expression: raise EmptyExpressionError() - previous_column = 0 - for match in self.master_regex.finditer(expression): - value = match.group() - start = match.start() - end = match.end() - if match.lastgroup == 'skip': - # Ignore whitespace. - previous_column = end - continue - if start != previous_column: - bad_value = expression[previous_column:start] - # Try to give a good error message. - if bad_value == '"': - raise LexerError( - lexer_position=previous_column, - lexer_value=value, - message='Starting quote is missing the ending quote', - expression=expression) - raise LexerError(lexer_position=previous_column, - lexer_value=value, - message='Unknown character', - expression=expression) - previous_column = end - token_type = match.lastgroup - handler = getattr(self, '_token_%s' % token_type.lower(), None) - if handler is not None: - value = handler(value, start, end) - yield {'type': token_type, 'value': value, - 'start': start, 'end': end} - # At the end of the loop make sure we've consumed all the input. - # If we haven't then we have unidentified characters. - if end != len(expression): - msg = "Unknown characters at the end of the expression" - raise LexerError(lexer_position=end, - lexer_value='', - message=msg, expression=expression) + self._position = 0 + self._expression = expression + self._chars = list(self._expression) + self._current = self._chars[self._position] + self._length = len(self._expression) + + def _next(self): + if self._position == self._length - 1: + self._current = None else: - yield {'type': 'eof', 'value': '', - 'start': len(expression), 'end': len(expression)} - - def _token_number(self, value, start, end): - return int(value) - - def _token_quoted_identifier(self, value, start, end): - try: - return loads(value) - except ValueError as e: - error_message = str(e).split(':')[0] - raise LexerError(lexer_position=start, - lexer_value=value, - message=error_message) - - def _token_string_literal(self, value, start, end): - return value[1:-1] - - def _token_literal(self, value, start, end): - actual_value = value[1:-1] - actual_value = actual_value.replace('\\`', '`').lstrip() - # First, if it looks like JSON then we parse it as - # JSON and any json parsing errors propogate as lexing - # errors. - if self._looks_like_json(actual_value): - try: - return loads(actual_value) - except ValueError: + self._position += 1 + self._current = self._chars[self._position] + return self._current + + def _consume_until(self, delimiter): + # Consume until the delimiter is reached, + # allowing for the delimiter to be escaped with "\". + start = self._position + buff = '' + self._next() + while self._current != delimiter: + if self._current == '\\': + buff += '\\' + self._next() + if self._current is None: raise LexerError(lexer_position=start, - lexer_value=value, - message="Bad token %s" % value) - else: - potential_value = '"%s"' % actual_value + lexer_value=self._expression, + message="Unclosed %s delimiter" % delimiter) + buff += self._current + self._next() + # Skip the closing delimiter. + self._next() + return buff + + def _consume_literal(self): + start = self._position + lexeme = self._consume_until('`').replace('\\`', '`') + try: + # Assume it is valid JSON and attempt to parse. + parsed_json = loads(lexeme) + except ValueError: try: - # There's a shortcut syntax where string literals - # don't have to be quoted. This is only true if the - # string doesn't start with chars that could start a valid - # JSON value. - value = loads(potential_value) + # Invalid JSON values should be converted to quoted + # JSON strings during the JEP-12 deprecation period. + parsed_json = loads('"%s"' % lexeme.lstrip()) warnings.warn("deprecated string literal syntax", PendingDeprecationWarning) - return value except ValueError: raise LexerError(lexer_position=start, - lexer_value=value, - message="Bad token %s" % value) + lexer_value=self._expression, + message="Bad token %s" % lexeme) + token_len = self._position - start + return {'type': 'literal', 'value': parsed_json, + 'start': start, 'end': token_len} + + def _consume_quoted_identifier(self): + start = self._position + lexeme = '"' + self._consume_until('"') + '"' + try: + token_len = self._position - start + return {'type': 'quoted_identifier', 'value': loads(lexeme), + 'start': start, 'end': token_len} + except ValueError as e: + error_message = str(e).split(':')[0] + raise LexerError(lexer_position=start, + lexer_value=lexeme, + message=error_message) - def _looks_like_json(self, value): - # Figure out if the string "value" starts with something - # that looks like json. - if not value: - return False - elif value[0] in ['"', '{', '[']: - return True - elif value in ['true', 'false', 'null']: - return True - elif value[0] in ['-', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9']: - # Then this is JSON, return True. - try: - loads(value) - return True - except ValueError: - return False - else: - return False + def _consume_raw_string_literal(self): + start = self._position + lexeme = self._consume_until("'").replace("\\'", "'") + token_len = self._position - start + return {'type': 'literal', 'value': lexeme, + 'start': start, 'end': token_len} + + def _match_or_else(self, expected, match_type, else_type): + start = self._position + current = self._current + next_char = self._next() + if next_char == expected: + self._next() + return {'type': match_type, 'value': current + next_char, + 'start': start, 'end': start + 1} + return {'type': else_type, 'value': current, + 'start': start, 'end': start} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/parser.py new/jmespath-0.9.0/jmespath/parser.py --- old/jmespath-0.7.1/jmespath/parser.py 2015-04-27 19:27:03.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/parser.py 2015-10-01 06:11:19.000000000 +0200 @@ -48,21 +48,27 @@ 'expref': 0, 'colon': 0, 'pipe': 1, - 'eq': 2, - 'gt': 2, - 'lt': 2, - 'gte': 2, - 'lte': 2, - 'ne': 2, - 'or': 5, - 'flatten': 6, + 'or': 2, + 'and': 3, + 'eq': 5, + 'gt': 5, + 'lt': 5, + 'gte': 5, + 'lte': 5, + 'ne': 5, + 'flatten': 9, + # Everything above stops a projection. 'star': 20, 'filter': 21, 'dot': 40, + 'not': 45, 'lbrace': 50, 'lbracket': 55, 'lparen': 60, } + # The maximum binding power for a token that can stop + # a projection. + _PROJECTION_STOP = 10 # The _MAX_SIZE most recent expressions are cached in # _CACHE dict. _CACHE = {} @@ -161,12 +167,21 @@ def _token_nud_lbrace(self, token): return self._parse_multi_select_hash() + def _token_nud_lparen(self, token): + expression = self._expression() + self._match('rparen') + return expression + def _token_nud_flatten(self, token): left = ast.flatten(ast.identity()) right = self._parse_projection_rhs( self.BINDING_POWER['flatten']) return ast.projection(left, right) + def _token_nud_not(self, token): + expr = self._expression(self.BINDING_POWER['not']) + return ast.not_expression(expr) + def _token_nud_lbracket(self, token): if self._current_token() in ['number', 'colon']: right = self._parse_index_expression() @@ -254,15 +269,15 @@ right = self._expression(self.BINDING_POWER['or']) return ast.or_expression(left, right) + def _token_led_and(self, left): + right = self._expression(self.BINDING_POWER['and']) + return ast.and_expression(left, right) + def _token_led_lparen(self, left): name = left['value'] args = [] while not self._current_token() == 'rparen': - if self._current_token() == 'current': - expression = ast.current_node() - self._advance() - else: - expression = self._expression() + expression = self._expression() if self._current_token() == 'comma': self._match('comma') args.append(expression) @@ -338,12 +353,13 @@ def _parse_multi_select_list(self): expressions = [] - while not self._current_token() == 'rbracket': + while True: expression = self._expression() expressions.append(expression) - if self._current_token() == 'comma': + if self._current_token() == 'rbracket': + break + else: self._match('comma') - self._assert_not_token('rbracket') self._match('rbracket') return ast.multi_select_list(expressions) @@ -369,7 +385,7 @@ def _parse_projection_rhs(self, binding_power): # Parse the right hand side of the projection. - if self.BINDING_POWER[self._current_token()] < 10: + if self.BINDING_POWER[self._current_token()] < self._PROJECTION_STOP: # BP of 10 are all the tokens that stop a projection. right = ast.identity() elif self._current_token() == 'lbracket': @@ -503,8 +519,8 @@ self.expression = expression self.parsed = parsed - def search(self, value): - interpreter = visitor.TreeInterpreter() + def search(self, value, options=None): + interpreter = visitor.TreeInterpreter(options) result = interpreter.visit(self.parsed, value) return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath/visitor.py new/jmespath-0.9.0/jmespath/visitor.py --- old/jmespath-0.7.1/jmespath/visitor.py 2015-04-14 01:16:58.000000000 +0200 +++ new/jmespath-0.9.0/jmespath/visitor.py 2015-10-01 06:11:19.000000000 +0200 @@ -33,6 +33,20 @@ return x is True or x is False +class Options(object): + """Options to control how a JMESPath function is evaluated.""" + def __init__(self, dict_cls): + #: The class to use when creating a dict. The interpreter + # may create dictionaries during the evalution of a JMESPath + # expression. For example, a multi-select hash will + # create a dictionary. By default we use a dict() type. + # You can set this value to change what dict type is used. + # The most common reason you would change this is if you + # want to set a collections.OrderedDict so that you can + # have predictible key ordering. + self.dict_cls = dict_cls + + class _Expression(object): def __init__(self, expression): self.expression = expression @@ -67,14 +81,21 @@ } MAP_TYPE = dict - def __init__(self): + def __init__(self, options=None): super(TreeInterpreter, self).__init__() + self._options = options + self._dict_cls = self.MAP_TYPE + if options is not None and options.dict_cls is not None: + self._dict_cls = self._options.dict_cls self._functions = functions.RuntimeFunctions() # Note that .interpreter is a property that uses # a weakref so that the cyclic reference can be # properly freed. self._functions.interpreter = self + def default_visit(self, node, *args, **kwargs): + raise NotImplementedError(node['type']) + def visit_subexpression(self, node, value): result = value for node in node['children']: @@ -114,7 +135,7 @@ comparator_node = node['children'][2] collected = [] for element in base: - if self.visit(comparator_node, element): + if self._is_true(self.visit(comparator_node, element)): current = self.visit(node['children'][1], element) if current is not None: collected.append(current) @@ -167,7 +188,7 @@ def visit_multi_select_dict(self, node, value): if value is None: return None - collected = self.MAP_TYPE() + collected = self._dict_cls() for child in node['children']: collected[child['value']] = self.visit(child, value) return collected @@ -186,6 +207,20 @@ matched = self.visit(node['children'][1], value) return matched + def visit_and_expression(self, node, value): + matched = self.visit(node['children'][0], value) + if self._is_false(matched): + return matched + return self.visit(node['children'][1], value) + + def visit_not_expression(self, node, value): + original_result = self.visit(node['children'][0], value) + if original_result is 0: + # Special case for 0, !0 should be false, not true. + # 0 is not a special cased integer in jmespath. + return False + return not original_result + def visit_pipe(self, node, value): result = value for node in node['children']: @@ -223,6 +258,9 @@ return (value == '' or value == [] or value == {} or value is None or value is False) + def _is_true(self, value): + return not self._is_false(value) + class GraphvizVisitor(Visitor): def __init__(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/PKG-INFO new/jmespath-0.9.0/jmespath.egg-info/PKG-INFO --- old/jmespath-0.7.1/jmespath.egg-info/PKG-INFO 2015-04-27 19:29:56.000000000 +0200 +++ new/jmespath-0.9.0/jmespath.egg-info/PKG-INFO 2015-10-01 06:12:12.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: jmespath -Version: 0.7.1 +Version: 0.9.0 Summary: JSON Matching Expressions Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie @@ -77,6 +77,26 @@ 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:: + + >>> 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('{a: a, b: b}, + ... mydata, + ... jmespath.Options(dict_cls=collections.OrderedDict)) + Specification ============= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt new/jmespath-0.9.0/jmespath.egg-info/SOURCES.txt --- old/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt 2015-04-27 19:29:56.000000000 +0200 +++ new/jmespath-0.9.0/jmespath.egg-info/SOURCES.txt 2015-10-01 06:12:12.000000000 +0200 @@ -15,6 +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 tests/__init__.py tests/test_compliance.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/pbr.json new/jmespath-0.9.0/jmespath.egg-info/pbr.json --- old/jmespath-0.7.1/jmespath.egg-info/pbr.json 1970-01-01 01:00:00.000000000 +0100 +++ new/jmespath-0.9.0/jmespath.egg-info/pbr.json 2015-04-21 08:34:36.000000000 +0200 @@ -0,0 +1 @@ +{"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.1/setup.py new/jmespath-0.9.0/setup.py --- old/jmespath-0.7.1/setup.py 2015-04-27 19:28:36.000000000 +0200 +++ new/jmespath-0.9.0/setup.py 2015-10-01 06:11:26.000000000 +0200 @@ -9,7 +9,7 @@ setup( name='jmespath', - version='0.7.1', + version='0.9.0', description='JSON Matching Expressions', long_description=io.open('README.rst', encoding='utf-8').read(), author='James Saryerwinnie', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jmespath-0.7.1/tests/test_compliance.py new/jmespath-0.9.0/tests/test_compliance.py --- old/jmespath-0.7.1/tests/test_compliance.py 2015-04-27 19:23:22.000000000 +0200 +++ new/jmespath-0.9.0/tests/test_compliance.py 2015-10-01 06:11:19.000000000 +0200 @@ -6,14 +6,14 @@ from nose.tools import assert_equal import jmespath -from jmespath.visitor import TreeInterpreter +from jmespath.visitor import TreeInterpreter, 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() -TreeInterpreter.MAP_TYPE = OrderedDict +OPTIONS = Options(dict_cls=OrderedDict) def test_compliance(): @@ -65,13 +65,13 @@ raise AssertionError( 'jmespath expression failed to compile: "%s", error: %s"' % (expression, e)) - actual = parsed.search(given) + 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, parsed, + actual_repr, pformat(parsed.parsed), json.dumps(given, indent=4))) error_msg = error_msg.replace(r'\n', '\n') assert_equal(actual, expected, error_msg) @@ -91,6 +91,6 @@ 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)) + filename, expression, pformat(parsed.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.1/tests/test_parser.py new/jmespath-0.9.0/tests/test_parser.py --- old/jmespath-0.7.1/tests/test_parser.py 2015-04-27 19:23:22.000000000 +0200 +++ new/jmespath-0.9.0/tests/test_parser.py 2015-10-01 06:11:19.000000000 +0200 @@ -1,9 +1,10 @@ #!/usr/bin/env python import re -from tests import unittest +from tests import unittest, OrderedDict from jmespath import parser +from jmespath import visitor from jmespath import ast from jmespath import exceptions @@ -94,6 +95,15 @@ 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): @@ -144,18 +154,12 @@ def test_bad_lexer_values(self): error_message = ( 'Bad jmespath expression: ' - 'Starting quote is missing the ending quote:\n' + 'Unclosed " delimiter:\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, @@ -326,6 +330,18 @@ 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()