jmespath-0.2.1/0000755€BÈÀ´€q{Ì0000000000012254676675017367 5ustar jamessarANT\Domain Users00000000000000jmespath-0.2.1/bin/0000755€BÈÀ´€q{Ì0000000000012254676675020137 5ustar jamessarANT\Domain Users00000000000000jmespath-0.2.1/bin/jp0000755€BÈÀ´€q{Ì0000000064112230063431020445 0ustar jamessarANT\Domain Users00000000000000#!/usr/bin/env python import jmespath import sys import json if not len(sys.argv) >= 2: sys.stderr.write('usage: jp \n\n') sys.exit(1) expression = sys.argv[1] if len(sys.argv) == 3: with open(sys.argv[2], 'r') as f: data = json.load(f) else: data = json.load(sys.stdin) sys.stdout.write(json.dumps(jmespath.search(expression, data), indent=4)) sys.stdout.write('\n') jmespath-0.2.1/jmespath/0000755€BÈÀ´€q{Ì0000000000012254676675021202 5ustar jamessarANT\Domain Users00000000000000jmespath-0.2.1/jmespath/__init__.py0000644€BÈÀ´€q{Ì0000000040512254676620023300 0ustar jamessarANT\Domain Users00000000000000from jmespath import parser __version__ = '0.2.1' def compile(expression, debug=False): return parser.Parser(debug=debug).parse(expression) def search(expression, data, debug=False): return parser.Parser(debug=debug).parse(expression).search(data) jmespath-0.2.1/jmespath/ast.py0000644€BÈÀ´€q{Ì0000001665712254676563022356 0ustar jamessarANT\Domain Users00000000000000class AST(object): VALUE_METHODS = [] def search(self, value): pass def _get_value_method(self, value): # This will find the appropriate getter method # based on the passed in value. for method_name in self.VALUE_METHODS: method = getattr(value, method_name, None) if method is not None: return method def pretty_print(self, indent=''): return super(AST, self).__repr__() def __repr__(self): return self.pretty_print() def __eq__(self, other): return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__) def __ne__(self, other): return not self.__eq__(other) class SubExpression(AST): """Represents a subexpression match. A subexpression match has a parent and a child node. A simple example would be something like 'foo.bar' which is represented as:: SubExpression(Field(foo), Field(bar)) """ def __init__(self, parent, child): self.parent = parent self.child = child def search(self, value): # To evaluate a subexpression we first evaluate the parent object # and then feed the match of the parent node into the child node. sub_value = self.parent.search(value) found = self.child.search(sub_value) return found def pretty_print(self, indent=''): sub_indent = indent + ' ' * 4 return "%sSubExpression(\n%s%s,\n%s%s)" % ( indent, sub_indent, self.parent.pretty_print(sub_indent), sub_indent, self.child.pretty_print(sub_indent)) class Field(AST): VALUE_METHODS = ['get'] def __init__(self, name): self.name = name def pretty_print(self, indent=''): return "%sField(%s)" % (indent, self.name) def search(self, value): method = self._get_value_method(value) if method is not None: return method(self.name) class BaseMultiField(AST): def __init__(self, nodes): self.nodes = nodes def search(self, value): if value is None: return None method = self._get_value_method(value) if method is not None: return method(self.nodes) else: return self._multi_get(value) def pretty_print(self, indent=''): return "%s%s(%s)" % (indent, self.__class__.__name__, self.nodes) class MultiFieldDict(BaseMultiField): VALUE_METHODS = ['multi_get'] def _multi_get(self, value): collected = {} for node in self.nodes: collected[node.key_name] = node.search(value) return collected class MultiFieldList(BaseMultiField): VALUE_METHODS = ['multi_get_list'] def _multi_get(self, value): collected = [] for node in self.nodes: collected.append(node.search(value)) return collected class KeyValPair(AST): def __init__(self, key_name, node): self.key_name = key_name self.node = node def search(self, value): return self.node.search(value) def pretty_print(self, indent=''): return "%sKeyValPair(key_name=%s, node=%s)" % (indent, self.key_name, self.node) class Index(AST): VALUE_METHODS = ['get_index', '__getitem__'] def __init__(self, index): self.index = index def pretty_print(self, indent=''): return "%sIndex(%s)" % (indent, self.index) def search(self, value): # Even though we can index strings, we don't # want to support that. if not isinstance(value, list): return None method = self._get_value_method(value) if method is not None: try: return method(self.index) except IndexError: pass class WildcardIndex(AST): """Represents a wildcard index. For example:: foo[*] -> SubExpression(Field(foo), WildcardIndex()) """ def search(self, value): if not isinstance(value, list): return None return _Projection(value) def pretty_print(self, indent=''): return "%sIndex(*)" % indent class WildcardValues(AST): """Represents a wildcard on the values of a JSON object. For example:: foo.* -> SubExpression(Field(foo), WildcardValues()) """ def search(self, value): try: return _Projection(value.values()) except AttributeError: return None def pretty_print(self, indent=''): return "%sWildcardValues()" % indent class ListElements(AST): def search(self, value): if isinstance(value, list): # reduce inner list elements into # a single list. merged_list = [] for element in value: if isinstance(element, list): merged_list.extend(element) else: merged_list.append(element) return _Projection(merged_list) else: return None def pretty_print(self, indent=''): return "%sListElements()" % indent class _Projection(list): def __init__(self, elements): self.extend(elements) def get(self, value): results = self.__class__([]) for element in self: try: result = element.get(value) except AttributeError: continue if result is not None: if isinstance(result, list): result = self.__class__(result) results.append(result) return results def get_index(self, index): matches = [] for el in self: if not isinstance(el, list): continue try: matches.append(el[index]) except (IndexError, TypeError): pass return self.__class__(matches) def multi_get(self, nodes): results = self.__class__([]) for element in self: if isinstance(element, self.__class__): result = element.multi_get(nodes) else: result = {} for node in nodes: result[node.key_name] = node.search(element) results.append(result) return results def multi_get_list(self, nodes): results = self.__class__([]) for element in self: if isinstance(element, self.__class__): result = element.multi_get_list(nodes) else: result = [] for node in nodes: result.append(node.search(element)) results.append(result) return results def values(self): results = self.__class__([]) for element in self: try: current = self.__class__(element.values()) results.append(current) except AttributeError: continue return results class ORExpression(AST): def __init__(self, first, remaining): self.first = first self.remaining = remaining def search(self, value): matched = self.first.search(value) if matched is None: matched = self.remaining.search(value) return matched def pretty_print(self, indent=''): return "%sORExpression(%s, %s)" % (indent, self.first, self.remaining) jmespath-0.2.1/jmespath/compat.py0000644€BÈÀ´€q{Ì0000000105512230063431023006 0ustar jamessarANT\Domain Users00000000000000import sys PY2 = sys.version_info[0] == 2 if PY2: def with_str_method(cls): """Class decorator that handles __str__ compat between py2 and py3.""" # In python2, the __str__ should be __unicode__ # and __str__ should return bytes. cls.__unicode__ = cls.__str__ def __str__(self): return self.__unicode__().encode('utf-8') cls.__str__ = __str__ return cls else: def with_str_method(cls): # In python3, we don't need to do anything, we return a str type. return cls jmespath-0.2.1/jmespath/lexer.py0000644€BÈÀ´€q{Ì0000000404412254676564022672 0ustar jamessarANT\Domain Users00000000000000import re from json import loads from jmespath.compat import with_str_method @with_str_method class LexerError(ValueError): def __init__(self, lexer_position, lexer_value, message): self.lexer_position = lexer_position self.lexer_value = lexer_value self.message = message super(LexerError, self).__init__(lexer_position, lexer_value, message) # Whatever catches LexerError can set this. self.expression = None def __str__(self): underline = ' ' * self.lexer_position + '^' return 'Bad jmespath expression: %s:\n%s\n%s' % ( self.message, self.expression, underline) class LexerDefinition(object): reflags = re.DOTALL reserved = {} tokens = ( 'STAR', 'DOT', 'LBRACKET', 'RBRACKET', 'LBRACE', 'RBRACE', 'OR', 'NUMBER', 'IDENTIFIER', 'COMMA', 'COLON', ) + tuple(reserved.values()) t_STAR = r'\*' t_DOT = r'\.' t_LBRACKET = r'\[' t_RBRACKET = r'\]' t_LBRACE = r'\{' t_RBRACE = r'\}' t_OR = r'\|\|' t_COMMA = r',' t_COLON = r':' t_ignore = ' ' def t_NUMBER(self, t): r'-?\d+' t.value = int(t.value) return t def t_IDENTIFIER(self, t): r'(([a-zA-Z_][a-zA-Z_0-9]*)|("(?:\\"|[^"])*"))' t.type = self.reserved.get(t.value, 'IDENTIFIER') i = 0 if t.value[0] == '"' and t.value[-1] == '"': t.value = loads(t.value) return t return t def t_error(self, t): # Try to be helpful in the case where they have a missing # quote char. if t.value.startswith('"'): raise LexerError( lexer_position=t.lexpos, lexer_value=t.value, message=("Bad token '%s': starting quote is missing " "the ending quote" % t.value)) raise ValueError("Illegal token value '%s'" % t.value) jmespath-0.2.1/jmespath/parser.py0000644€BÈÀ´€q{Ì0000001446212254676564023054 0ustar jamessarANT\Domain Users00000000000000import random import ply.yacc import ply.lex from jmespath import ast from jmespath import lexer from jmespath.compat import with_str_method @with_str_method class ParseError(ValueError): def __init__(self, lex_position, token_value, token_type): super(ParseError, self).__init__(lex_position, token_value, token_type) self.lex_position = lex_position self.token_value = token_value self.token_type = token_type # Whatever catches the ParseError can fill in the full expression self.expression = None def __str__(self): # self.lex_position +1 to account for the starting double quote char. underline = ' ' * (self.lex_position + 1) + '^' return ( 'Invalid jmespath expression: Parse error at column %s near ' 'token "%s" (%s) for expression:\n"%s"\n%s' % ( self.lex_position, self.token_value, self.token_type, self.expression, underline)) class IncompleteExpressionError(ParseError): def set_expression(self, expression): self.expression = expression self.lex_position = len(expression) self.token_type = None self.token_value = None def __str__(self): # self.lex_position +1 to account for the starting double quote char. underline = ' ' * (self.lex_position + 1) + '^' return ( 'Invalid jmespath expression: Incomplete expression:\n' '"%s"\n%s' % (self.expression, underline)) class Grammar(object): precedence = ( ('right', 'DOT', 'LBRACKET'), ) def p_jmespath_subexpression(self, p): """ expression : expression DOT expression | STAR """ if len(p) == 2: # Then this is the STAR rule. p[0] = ast.WildcardValues() else: # This is the expression DOT expression rule. p[0] = ast.SubExpression(p[1], p[3]) def p_jmespath_index(self, p): """expression : expression bracket-spec | bracket-spec """ if len(p) == 3: p[0] = ast.SubExpression(p[1], p[2]) elif len(p) == 2: # Otherwise this is just a bracket-spec, which is valid as a root # level node (e.g. [2]) so we just assign the root node to the # bracket-spec. p[0] = p[1] def p_jmespath_bracket_specifier(self, p): """bracket-spec : LBRACKET STAR RBRACKET | LBRACKET NUMBER RBRACKET | LBRACKET RBRACKET """ if len(p) == 3: p[0] = ast.ListElements() elif p[2] == '*': p[0] = ast.WildcardIndex() else: p[0] = ast.Index(p[2]) def p_jmespath_identifier(self, p): """expression : IDENTIFIER | NUMBER """ p[0] = ast.Field(p[1]) def p_jmespath_multiselect(self, p): """expression : LBRACE keyval-exprs RBRACE """ p[0] = ast.MultiFieldDict(p[2]) def p_jmespath_multiselect_list(self, p): """expression : LBRACKET expressions RBRACKET """ p[0] = ast.MultiFieldList(p[2]) def p_jmespath_keyval_exprs(self, p): """keyval-exprs : keyval-exprs COMMA keyval-expr | keyval-expr """ if len(p) == 2: p[0] = [p[1]] elif len(p) == 4: p[1].append(p[3]) p[0] = p[1] def p_jmespath_keyval_expr(self, p): """keyval-expr : IDENTIFIER COLON expression """ p[0] = ast.KeyValPair(p[1], p[3]) def p_jmespath_multiple_expressions(self, p): """expressions : expressions COMMA expression | expression """ if len(p) == 2: p[0] = [p[1]] elif len(p) == 4: p[1].append(p[3]) p[0] = p[1] def p_jmespath_or_expression(self, p): """expression : expression OR expression""" p[0] = ast.ORExpression(p[1], p[3]) def p_error(self, t): if t is not None: raise ParseError(t.lexpos, t.value, t.type) else: raise IncompleteExpressionError(None, None, None) class Parser(object): # The _max_size most recent expressions are cached in # _cache dict. _cache = {} _max_size = 64 def __init__(self, lexer_definition=None, grammar=None, debug=False): if lexer_definition is None: lexer_definition = lexer.LexerDefinition if grammar is None: grammar = Grammar self._lexer_definition = lexer_definition self._grammar = grammar self.tokens = self._lexer_definition.tokens self._debug = debug def parse(self, expression): cached = self._cache.get(expression) if cached is not None: return cached lexer = ply.lex.lex(module=self._lexer_definition(), debug=self._debug, reflags=self._lexer_definition.reflags) grammar = self._grammar() grammar.tokens = self._lexer_definition.tokens parser = ply.yacc.yacc(module=grammar, debug=self._debug, write_tables=False) parsed = self._parse_expression(parser=parser, expression=expression, lexer_obj=lexer) self._cache[expression] = parsed if len(self._cache) > self._max_size: self._free_cache_entries() return parsed def _parse_expression(self, parser, expression, lexer_obj): try: parsed = parser.parse(input=expression, lexer=lexer_obj) return parsed except lexer.LexerError as e: e.expression = expression raise e except IncompleteExpressionError as e: e.set_expression(expression) raise e except ParseError as e: e.expression = expression raise e def _free_cache_entries(self): # This logic is borrowed from the new regex library which # uses similar eviction strategies. for key in random.sample(self._cache.keys(), int(self._max_size / 2)): del self._cache[key] @classmethod def purge(cls): """Clear the expression compilation cache.""" cls._cache.clear() jmespath-0.2.1/jmespath.egg-info/0000755€BÈÀ´€q{Ì0000000000012254676675022674 5ustar jamessarANT\Domain Users00000000000000jmespath-0.2.1/jmespath.egg-info/dependency_links.txt0000644€BÈÀ´€q{Ì0000000000112254676675026742 0ustar jamessarANT\Domain Users00000000000000 jmespath-0.2.1/jmespath.egg-info/PKG-INFO0000644€BÈÀ´€q{Ì0000000743112254676675023776 0ustar jamessarANT\Domain Users00000000000000Metadata-Version: 1.1 Name: jmespath Version: 0.2.1 Summary: JSON Matching Expressions Home-page: https://github.com/boto/jmespath Author: James Saryerwinnie Author-email: js@jamesls.com License: UNKNOWN Description: JMESPath ======== JMESPath (pronounced "jaymz path") allows you to declaratively specify how to extract elements from a JSON document. For example, given this document:: {"foo": {"bar": "baz"}} The jmespath expression ``foo.bar`` will return "baz". JMESPath also supports: Referencing elements in a list. Given the data:: {"foo": {"bar": ["one", "two"]}} The expression: ``foo.bar[0]`` will return "one". You can also reference all the items in a list using the ``*`` syntax:: {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} The expression: ``foo.bar[*].name`` will return ["one", "two"]. Negative indexing is also supported (-1 refers to the last element in the list). Given the data above, the expression ``foo.bar[-1].name`` will return ["two"]. The ``*`` can also be used for hash types:: {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} The expression: ``foo.*.name`` will return ["one", "two"]. **NOTE: jmespath is being actively developed. There are a number of features it does not currently support that may be added in the future.** Specification ============= The grammar is specified using ABNF, as described in `RFC4234`_. You can find the most up to date grammar for JMESPath `here `__. You can read the full JMESPath specification `here http://jmespath.readthedocs.org/en/latest/specification.html`__. Testing ======= In addition to the unit tests for the jmespath modules, there is a ``tests/compliance`` directory that contains .json files with test cases. This allows other implementations to verify they are producing the correct output. Each json file is grouped by feature. Python Library ============== The included python implementation has two convenience functions that operate on python data structures. You can use ``search`` and give it the jmespath expression and the data:: >>> import jmespath >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) 'baz' Similar to the ``re`` module, you can store the compiled expressions and reuse them to perform repeated searches:: >>> import jmespath >>> path = jmespath.compile('foo.bar') >>> path.search({'foo': {'bar': 'baz'}}) 'baz' >>> path.search({'foo': {'bar': 'other'}}) 'other' You can also use the ``jmespath.parser.Parser`` class directly if you want more control. .. _RFC4234: http://tools.ietf.org/html/rfc4234 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 jmespath-0.2.1/jmespath.egg-info/requires.txt0000644€BÈÀ´€q{Ì0000000001012254676675025263 0ustar jamessarANT\Domain Users00000000000000ply==3.4jmespath-0.2.1/jmespath.egg-info/SOURCES.txt0000644€BÈÀ´€q{Ì0000000060012254676675024554 0ustar jamessarANT\Domain Users00000000000000LICENSE.txt MANIFEST.in README.rst setup.py bin/jp jmespath/__init__.py jmespath/ast.py jmespath/compat.py jmespath/lexer.py jmespath/parser.py jmespath.egg-info/PKG-INFO jmespath.egg-info/SOURCES.txt jmespath.egg-info/dependency_links.txt jmespath.egg-info/requires.txt jmespath.egg-info/top_level.txt tests/__init__.py tests/test_ast.py tests/test_compliance.py tests/test_parser.pyjmespath-0.2.1/jmespath.egg-info/top_level.txt0000644€BÈÀ´€q{Ì0000000001112254676675025416 0ustar jamessarANT\Domain Users00000000000000jmespath jmespath-0.2.1/LICENSE.txt0000644€BÈÀ´€q{Ì0000000175712110775536021207 0ustar jamessarANT\Domain Users00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, dis- tribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the fol- lowing conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jmespath-0.2.1/MANIFEST.in0000644€BÈÀ´€q{Ì0000000004712110775536021111 0ustar jamessarANT\Domain Users00000000000000include README.rst include LICENSE.txt jmespath-0.2.1/PKG-INFO0000644€BÈÀ´€q{Ì0000000743112254676675020471 0ustar jamessarANT\Domain Users00000000000000Metadata-Version: 1.1 Name: jmespath Version: 0.2.1 Summary: JSON Matching Expressions Home-page: https://github.com/boto/jmespath Author: James Saryerwinnie Author-email: js@jamesls.com License: UNKNOWN Description: JMESPath ======== JMESPath (pronounced "jaymz path") allows you to declaratively specify how to extract elements from a JSON document. For example, given this document:: {"foo": {"bar": "baz"}} The jmespath expression ``foo.bar`` will return "baz". JMESPath also supports: Referencing elements in a list. Given the data:: {"foo": {"bar": ["one", "two"]}} The expression: ``foo.bar[0]`` will return "one". You can also reference all the items in a list using the ``*`` syntax:: {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} The expression: ``foo.bar[*].name`` will return ["one", "two"]. Negative indexing is also supported (-1 refers to the last element in the list). Given the data above, the expression ``foo.bar[-1].name`` will return ["two"]. The ``*`` can also be used for hash types:: {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} The expression: ``foo.*.name`` will return ["one", "two"]. **NOTE: jmespath is being actively developed. There are a number of features it does not currently support that may be added in the future.** Specification ============= The grammar is specified using ABNF, as described in `RFC4234`_. You can find the most up to date grammar for JMESPath `here `__. You can read the full JMESPath specification `here http://jmespath.readthedocs.org/en/latest/specification.html`__. Testing ======= In addition to the unit tests for the jmespath modules, there is a ``tests/compliance`` directory that contains .json files with test cases. This allows other implementations to verify they are producing the correct output. Each json file is grouped by feature. Python Library ============== The included python implementation has two convenience functions that operate on python data structures. You can use ``search`` and give it the jmespath expression and the data:: >>> import jmespath >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) 'baz' Similar to the ``re`` module, you can store the compiled expressions and reuse them to perform repeated searches:: >>> import jmespath >>> path = jmespath.compile('foo.bar') >>> path.search({'foo': {'bar': 'baz'}}) 'baz' >>> path.search({'foo': {'bar': 'other'}}) 'other' You can also use the ``jmespath.parser.Parser`` class directly if you want more control. .. _RFC4234: http://tools.ietf.org/html/rfc4234 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 jmespath-0.2.1/README.rst0000644€BÈÀ´€q{Ì0000000464712254676563021065 0ustar jamessarANT\Domain Users00000000000000JMESPath ======== JMESPath (pronounced "jaymz path") allows you to declaratively specify how to extract elements from a JSON document. For example, given this document:: {"foo": {"bar": "baz"}} The jmespath expression ``foo.bar`` will return "baz". JMESPath also supports: Referencing elements in a list. Given the data:: {"foo": {"bar": ["one", "two"]}} The expression: ``foo.bar[0]`` will return "one". You can also reference all the items in a list using the ``*`` syntax:: {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} The expression: ``foo.bar[*].name`` will return ["one", "two"]. Negative indexing is also supported (-1 refers to the last element in the list). Given the data above, the expression ``foo.bar[-1].name`` will return ["two"]. The ``*`` can also be used for hash types:: {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} The expression: ``foo.*.name`` will return ["one", "two"]. **NOTE: jmespath is being actively developed. There are a number of features it does not currently support that may be added in the future.** Specification ============= The grammar is specified using ABNF, as described in `RFC4234`_. You can find the most up to date grammar for JMESPath `here `__. You can read the full JMESPath specification `here http://jmespath.readthedocs.org/en/latest/specification.html`__. Testing ======= In addition to the unit tests for the jmespath modules, there is a ``tests/compliance`` directory that contains .json files with test cases. This allows other implementations to verify they are producing the correct output. Each json file is grouped by feature. Python Library ============== The included python implementation has two convenience functions that operate on python data structures. You can use ``search`` and give it the jmespath expression and the data:: >>> import jmespath >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) 'baz' Similar to the ``re`` module, you can store the compiled expressions and reuse them to perform repeated searches:: >>> import jmespath >>> path = jmespath.compile('foo.bar') >>> path.search({'foo': {'bar': 'baz'}}) 'baz' >>> path.search({'foo': {'bar': 'other'}}) 'other' You can also use the ``jmespath.parser.Parser`` class directly if you want more control. .. _RFC4234: http://tools.ietf.org/html/rfc4234 jmespath-0.2.1/setup.cfg0000644€BÈÀ´€q{Ì0000000007312254676675021210 0ustar jamessarANT\Domain Users00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 jmespath-0.2.1/setup.py0000644€BÈÀ´€q{Ì0000000167112254676620021074 0ustar jamessarANT\Domain Users00000000000000#!/usr/bin/env python import os import sys from setuptools import setup, find_packages setup( name='jmespath', version='0.2.1', description='JSON Matching Expressions', long_description=open('README.rst').read(), author='James Saryerwinnie', author_email='js@jamesls.com', url='https://github.com/boto/jmespath', scripts=['bin/jp'], packages=find_packages(exclude=['tests']), install_requires=[ 'ply==3.4', ], classifiers=( 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', ), ) jmespath-0.2.1/tests/0000755€BÈÀ´€q{Ì0000000000012254676675020531 5ustar jamessarANT\Domain Users00000000000000jmespath-0.2.1/tests/__init__.py0000644€BÈÀ´€q{Ì0000000055012113443513022613 0ustar jamessarANT\Domain Users00000000000000import sys # 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 jmespath-0.2.1/tests/test_ast.py0000644€BÈÀ´€q{Ì0000002676312250211733022717 0ustar jamessarANT\Domain Users00000000000000#!/usr/bin/env python import unittest from tests import OrderedDict from jmespath import ast class TestAST(unittest.TestCase): def setUp(self): pass def test_field(self): # jmespath: foo field = ast.Field('foo') match = field.search({'foo': 'bar'}) self.assertEqual(match, 'bar') def test_field_no_match(self): # jmespath: foo field = ast.Field('foo') match = field.search({'bar': 'bar'}) self.assertEqual(match, None) def test_field_when_dict(self): # jmespath: foo field = ast.Field('foo') match = field.search({'foo': {'bar': 'baz'}}) self.assertEqual(match, {'bar': 'baz'}) def test_field_when_list(self): # jmespath: foo field = ast.Field('foo') match = field.search({'foo': ['bar', 'baz']}) self.assertEqual(match, ['bar', 'baz']) def test_dot_syntax(self): # jmespath: foo.bar child = ast.SubExpression(ast.Field('foo'), ast.Field('bar')) match = child.search({'foo': {'bar': 'correct', 'baz': 'wrong'}}) self.assertEqual(match, 'correct') def test_multiple_nestings(self): # jmespath: foo.bar.baz child = ast.SubExpression( ast.Field('foo'), ast.SubExpression(ast.Field('bar'), ast.Field('baz'))) match = child.search( {'foo': {'bar': {'baz': 'correct'}}}) self.assertEqual(match, 'correct') self.assertEqual( child.search({'foo': {'bar': {'wrong': 'wrong'}}}), None) self.assertEqual(child.search({}), None) self.assertEqual(child.search([]), None) self.assertEqual(child.search(''), None) def test_index(self): # jmespath: foo[1] child = ast.SubExpression(ast.Field('foo'), ast.Index(1)) match = child.search( {'foo': ['one', 'two', 'three']}) self.assertEqual(match, 'two') def test_bad_index(self): # jmespath: foo[100] child = ast.SubExpression(ast.Field('foo'), ast.Index(100)) match = child.search( {'foo': ['one', 'two', 'three']}) self.assertEqual(match, None) def test_negative_index(self): # jmespath: foo[-1] child = ast.SubExpression(ast.Field('foo'), ast.Index(-1)) match = child.search( {'foo': ['one', 'two', 'last']}) self.assertEqual(match, 'last') def test_index_with_children(self): # jmespath: foo.bar[-1] child = ast.SubExpression( ast.Field('foo'), ast.SubExpression(ast.Field('bar'), ast.Index(-1))) match = child.search( {'foo': {'bar': ['first', 'middle', 'last']}}) self.assertEqual(match, 'last') def test_multiple_indices(self): # jmespath: foo[1].bar[1] child = ast.SubExpression( ast.SubExpression( ast.Field('foo'), ast.Index(1)), ast.SubExpression( ast.Field('bar'), ast.Index(1))) match = child.search( {'foo': ['one', {'bar': ['zero', 'one']}]}) self.assertEqual(match, 'one') def test_index_with_star(self): # jmespath: foo[*] child = ast.SubExpression(ast.Field('foo'), ast.WildcardIndex()) match = child.search({'foo': ['one', 'two']}) self.assertEqual(match, ['one', 'two']) def test_associative(self): data = {'foo': {'bar': ['one']}} # jmespath: foo.bar[0] first = ast.SubExpression( ast.Field('foo'), ast.SubExpression(ast.Field('bar'), ast.Index(0))) second = ast.SubExpression( ast.SubExpression(ast.Field('foo'), ast.Field('bar')), ast.Index(0)) self.assertEqual(first.search(data), 'one') self.assertEqual(second.search(data), 'one') def test_wildcard_branches_on_dict_values(self): data = {'foo': {'bar': {'get': 'one'}, 'baz': {'get': 'two'}}} # ast for "foo.*.get" expression = ast.SubExpression( ast.SubExpression(ast.Field('foo'), ast.WildcardValues()), ast.Field('get')) match = expression.search(data) self.assertEqual(sorted(match), ['one', 'two']) self.assertEqual(expression.search({'foo': [{'bar': 'one'}]}), None) def test_wildcard_dot_wildcard(self): _ = OrderedDict data = _([( "top1", _({ "sub1": _({"foo": "one"}) })), ("top2", _({ "sub1": _({"foo": "two"}) })), ("top3", _({ "sub3": _({"notfoo": "notfoo"}) })) ]) # ast for "*.*" expression = ast.SubExpression( ast.WildcardValues(), ast.WildcardValues()) match = expression.search(data) self.assertEqual(match, [[{'foo': 'one'}], [{'foo': 'two'}], [{'notfoo': 'notfoo'}]]) def test_wildcard_with_field_node(self): data = { "top1": { "sub1": {"foo": "one"} }, "top2": { "sub1": {"foo": "two"} }, "top3": { "sub3": {"notfoo": "notfoo"} } } # ast for "*.*.foo" expression = ast.SubExpression( ast.WildcardValues(), ast.SubExpression(ast.WildcardValues(), ast.Field('foo'))) match = expression.search(data) self.assertEqual(sorted(match), sorted([[], ['one'], ['two']])) def test_wildcard_branches_with_index(self): # foo[*].bar child = ast.SubExpression( ast.SubExpression(ast.Field('foo'), ast.WildcardIndex()), ast.Field('bar') ) match = child.search( {'foo': [{'bar': 'one'}, {'bar': 'two'}]}) self.assertTrue(isinstance(match, list)) self.assertEqual(match, ['one', 'two']) def test_index_with_multi_match(self): # foo[*].bar[0] child = ast.SubExpression( ast.SubExpression(ast.Field('foo'), ast.WildcardIndex()), ast.SubExpression( ast.Field('bar'), ast.Index(0))) data = {'foo': [{'bar': ['one', 'two']}, {'bar': ['three', 'four']}]} match = child.search(data) self.assertEqual(match, ['one', 'three']) def test_or_expression(self): # foo or bar field_foo = ast.Field('foo') field_bar = ast.Field('bar') or_expression = ast.ORExpression(field_foo, field_bar) self.assertEqual(or_expression.search({'foo': 'foo'}), 'foo') self.assertEqual(or_expression.search({'bar': 'bar'}), 'bar') self.assertEqual(or_expression.search( {'foo': 'foo', 'bar': 'bar'}), 'foo') def test_multiselect_dict(self): # foo.{bar,baz field_foo = ast.KeyValPair(key_name='foo', node=ast.Field('foo')) field_bar = ast.KeyValPair(key_name='bar', node=ast.Field('bar')) field_baz = ast.KeyValPair(key_name='baz', node=ast.Field('baz')) multiselect = ast.MultiFieldDict([field_bar, field_baz]) subexpr = ast.SubExpression(field_foo, multiselect) self.assertEqual( subexpr.search({'foo': {'bar': 1, 'baz': 2, 'qux': 3}}), {'bar': 1, 'baz': 2}) def test_multiselect_different_key_names(self): field_foo = ast.KeyValPair(key_name='arbitrary', node=ast.Field('foo')) field_bar = ast.KeyValPair(key_name='arbitrary2', node=ast.Field('bar')) multiselect = ast.MultiFieldDict([field_foo, field_bar]) self.assertEqual(multiselect.search({'foo': 'value1', 'bar': 'value2'}), {'arbitrary': 'value1', 'arbitrary2': 'value2'}) def test_multiselect_list(self): # foo.[bar,baz] field_foo = ast.Field('foo') field_bar = ast.Field('bar') field_baz = ast.Field('baz') multiselect = ast.MultiFieldList([field_bar, field_baz]) subexpr = ast.SubExpression(field_foo, multiselect) self.assertEqual( subexpr.search({'foo': {'bar': 1, 'baz': 2, 'qux': 3}}), [1, 2]) def test_multiselect_list_wildcard(self): data = { 'foo': { 'ignore1': { 'one': 1, 'two': 2, 'three': 3, }, 'ignore2': { 'one': 1, 'two': 2, 'three': 3, }, } } expr = ast.SubExpression( ast.Field("foo"), ast.SubExpression( ast.WildcardValues(), ast.MultiFieldList([ast.Field("one"), ast.Field("two")]))) self.assertEqual(expr.search(data), [[1, 2], [1, 2]]) def test_wildcard_values_index_not_a_list(self): parsed = ast.SubExpression( ast.WildcardValues(), ast.SubExpression(ast.Field("foo"), ast.Index(0))) data = {"a": {"foo": 1}, "b": {"foo": 1}, "c": {"bar": 1}} self.assertEqual(parsed.search(data), []) def test_wildcard_values_index_does_exist(self): parsed = ast.SubExpression( ast.WildcardValues(), ast.SubExpression(ast.Field("foo"), ast.Index(0))) data = {"a": {"foo": [1]}, "b": {"foo": 1}, "c": {"bar": 1}} self.assertEqual(parsed.search(data), [1]) def test_flattened_wildcard(self): parsed = ast.SubExpression( # foo[].bar ast.SubExpression(ast.Field("foo"), ast.ListElements()), ast.Field("bar")) data = {'foo': [{'bar': 1}, {'bar': 2}, {'bar': 3}]} self.assertEqual(parsed.search(data), [1, 2, 3]) def test_multiple_nested_wildcards(self): # foo[].bar[].baz parsed = ast.SubExpression( ast.SubExpression( ast.Field("foo"), ast.ListElements()), ast.SubExpression( ast.SubExpression( ast.Field("bar"), ast.ListElements()), ast.Field("baz"))) data = { "foo": [ {"bar": [{"baz": 1}, {"baz": 2}]}, {"bar": [{"baz": 3}, {"baz": 4}]}, ] } self.assertEqual(parsed.search(data), [1, 2, 3, 4]) def test_multiple_nested_wildcards_with_list_values(self): parsed = ast.SubExpression( ast.SubExpression( ast.Field("foo"), ast.ListElements()), ast.SubExpression( ast.SubExpression( ast.Field("bar"), ast.ListElements()), ast.Field("baz"))) data = { "foo": [ {"bar": [{"baz": [1]}, {"baz": [2]}]}, {"bar": [{"baz": [3]}, {"baz": [4]}]}, ] } self.assertEqual(parsed.search(data), [[1], [2], [3], [4]]) def test_flattened_multiselect_list(self): # foo[].[bar,baz] field_foo = ast.Field('foo') parent = ast.SubExpression(field_foo, ast.ListElements()) field_bar = ast.Field('bar') field_baz = ast.Field('baz') multiselect = ast.MultiFieldList([field_bar, field_baz]) subexpr = ast.SubExpression(parent, multiselect) self.assertEqual( subexpr.search({'foo': [{'bar': 1, 'baz': 2, 'qux': 3}]}), [[1, 2]]) if __name__ == '__main__': unittest.main() jmespath-0.2.1/tests/test_compliance.py0000644€BÈÀ´€q{Ì0000000302312253502546024232 0ustar jamessarANT\Domain Users00000000000000import os from pprint import pformat from tests import OrderedDict from tests import json from nose.tools import assert_equal import jmespath TEST_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'compliance') def test_compliance(): for root, dirnames, filenames in os.walk(TEST_DIR): for filename in filenames: if filename.endswith('.json'): full_path = os.path.join(root, filename) for given, expression, result in _load_cases(full_path): yield _test_expression, given, expression, result, 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['result'] def _test_expression(given, expression, expected, filename): 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(%s) The expression '%s' was suppose to give: %s.\n" "Instead it matched: %s\nparsed as:\n%s" % ( filename, expression, expected_repr, actual_repr, parsed)) assert_equal(actual, expected, error_msg) jmespath-0.2.1/tests/test_parser.py0000644€BÈÀ´€q{Ì0000002422712254676564023442 0ustar jamessarANT\Domain Users00000000000000#!/usr/bin/env python from tests import unittest from jmespath import parser from jmespath import ast from jmespath import lexer class TestParser(unittest.TestCase): def setUp(self): self.parser = parser.Parser() def test_field(self): parsed = self.parser.parse('foo') self.assertEqual(parsed.search({'foo': 'bar'}), 'bar') def test_dot_syntax(self): parsed = self.parser.parse('foo.bar') self.assertEqual(parsed.search({'foo': {'bar': 'baz'}}), 'baz') 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): parsed = self.parser.parse('"foo"."bar"') self.assertIsInstance(parsed, ast.SubExpression) self.assertEqual(parsed.parent.name, 'foo') self.assertEqual(parsed.child.name, '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): parsed = self.parser.parse('foo || bar') self.assertEqual(repr(parsed), 'ORExpression(Field(foo), Field(bar))') 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}') foo = parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, '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=parser.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(parser.ParseError): parsed = self.parser.parse('foo]baz') def test_bad_parse_error_message(self): error_message = ( 'Invalid jmespath expression: Parse error at column 3 ' 'near 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_bad_lexer_values(self): error_message = ( 'Bad jmespath expression: Bad token \'"bar\': ' 'starting quote is missing the ending quote:\n' 'foo."bar\n' ' ^') self.assert_error_message('foo."bar', error_message, exception=lexer.LexerError) 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): parsed = 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(compiled, compiled2) if __name__ == '__main__': unittest.main()