visitor.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import operator
  2. from jmespath import functions
  3. from jmespath.compat import string_type
  4. from numbers import Number
  5. def _equals(x, y):
  6. if _is_special_integer_case(x, y):
  7. return False
  8. else:
  9. return x == y
  10. def _is_special_integer_case(x, y):
  11. # We need to special case comparing 0 or 1 to
  12. # True/False. While normally comparing any
  13. # integer other than 0/1 to True/False will always
  14. # return False. However 0/1 have this:
  15. # >>> 0 == True
  16. # False
  17. # >>> 0 == False
  18. # True
  19. # >>> 1 == True
  20. # True
  21. # >>> 1 == False
  22. # False
  23. #
  24. # Also need to consider that:
  25. # >>> 0 in [True, False]
  26. # True
  27. if x is 0 or x is 1:
  28. return y is True or y is False
  29. elif y is 0 or y is 1:
  30. return x is True or x is False
  31. def _is_comparable(x):
  32. # The spec doesn't officially support string types yet,
  33. # but enough people are relying on this behavior that
  34. # it's been added back. This should eventually become
  35. # part of the official spec.
  36. return _is_actual_number(x) or isinstance(x, string_type)
  37. def _is_actual_number(x):
  38. # We need to handle python's quirkiness with booleans,
  39. # specifically:
  40. #
  41. # >>> isinstance(False, int)
  42. # True
  43. # >>> isinstance(True, int)
  44. # True
  45. if x is True or x is False:
  46. return False
  47. return isinstance(x, Number)
  48. class Options(object):
  49. """Options to control how a JMESPath function is evaluated."""
  50. def __init__(self, dict_cls=None, custom_functions=None):
  51. #: The class to use when creating a dict. The interpreter
  52. # may create dictionaries during the evaluation of a JMESPath
  53. # expression. For example, a multi-select hash will
  54. # create a dictionary. By default we use a dict() type.
  55. # You can set this value to change what dict type is used.
  56. # The most common reason you would change this is if you
  57. # want to set a collections.OrderedDict so that you can
  58. # have predictable key ordering.
  59. self.dict_cls = dict_cls
  60. self.custom_functions = custom_functions
  61. class _Expression(object):
  62. def __init__(self, expression, interpreter):
  63. self.expression = expression
  64. self.interpreter = interpreter
  65. def visit(self, node, *args, **kwargs):
  66. return self.interpreter.visit(node, *args, **kwargs)
  67. class Visitor(object):
  68. def __init__(self):
  69. self._method_cache = {}
  70. def visit(self, node, *args, **kwargs):
  71. node_type = node['type']
  72. method = self._method_cache.get(node_type)
  73. if method is None:
  74. method = getattr(
  75. self, 'visit_%s' % node['type'], self.default_visit)
  76. self._method_cache[node_type] = method
  77. return method(node, *args, **kwargs)
  78. def default_visit(self, node, *args, **kwargs):
  79. raise NotImplementedError("default_visit")
  80. class TreeInterpreter(Visitor):
  81. COMPARATOR_FUNC = {
  82. 'eq': _equals,
  83. 'ne': lambda x, y: not _equals(x, y),
  84. 'lt': operator.lt,
  85. 'gt': operator.gt,
  86. 'lte': operator.le,
  87. 'gte': operator.ge
  88. }
  89. _EQUALITY_OPS = ['eq', 'ne']
  90. MAP_TYPE = dict
  91. def __init__(self, options=None):
  92. super(TreeInterpreter, self).__init__()
  93. self._dict_cls = self.MAP_TYPE
  94. if options is None:
  95. options = Options()
  96. self._options = options
  97. if options.dict_cls is not None:
  98. self._dict_cls = self._options.dict_cls
  99. if options.custom_functions is not None:
  100. self._functions = self._options.custom_functions
  101. else:
  102. self._functions = functions.Functions()
  103. def default_visit(self, node, *args, **kwargs):
  104. raise NotImplementedError(node['type'])
  105. def visit_subexpression(self, node, value):
  106. result = value
  107. for node in node['children']:
  108. result = self.visit(node, result)
  109. return result
  110. def visit_field(self, node, value):
  111. try:
  112. return value.get(node['value'])
  113. except AttributeError:
  114. return None
  115. def visit_comparator(self, node, value):
  116. # Common case: comparator is == or !=
  117. comparator_func = self.COMPARATOR_FUNC[node['value']]
  118. if node['value'] in self._EQUALITY_OPS:
  119. return comparator_func(
  120. self.visit(node['children'][0], value),
  121. self.visit(node['children'][1], value)
  122. )
  123. else:
  124. # Ordering operators are only valid for numbers.
  125. # Evaluating any other type with a comparison operator
  126. # will yield a None value.
  127. left = self.visit(node['children'][0], value)
  128. right = self.visit(node['children'][1], value)
  129. num_types = (int, float)
  130. if not (_is_comparable(left) and
  131. _is_comparable(right)):
  132. return None
  133. return comparator_func(left, right)
  134. def visit_current(self, node, value):
  135. return value
  136. def visit_expref(self, node, value):
  137. return _Expression(node['children'][0], self)
  138. def visit_function_expression(self, node, value):
  139. resolved_args = []
  140. for child in node['children']:
  141. current = self.visit(child, value)
  142. resolved_args.append(current)
  143. return self._functions.call_function(node['value'], resolved_args)
  144. def visit_filter_projection(self, node, value):
  145. base = self.visit(node['children'][0], value)
  146. if not isinstance(base, list):
  147. return None
  148. comparator_node = node['children'][2]
  149. collected = []
  150. for element in base:
  151. if self._is_true(self.visit(comparator_node, element)):
  152. current = self.visit(node['children'][1], element)
  153. if current is not None:
  154. collected.append(current)
  155. return collected
  156. def visit_flatten(self, node, value):
  157. base = self.visit(node['children'][0], value)
  158. if not isinstance(base, list):
  159. # Can't flatten the object if it's not a list.
  160. return None
  161. merged_list = []
  162. for element in base:
  163. if isinstance(element, list):
  164. merged_list.extend(element)
  165. else:
  166. merged_list.append(element)
  167. return merged_list
  168. def visit_identity(self, node, value):
  169. return value
  170. def visit_index(self, node, value):
  171. # Even though we can index strings, we don't
  172. # want to support that.
  173. if not isinstance(value, list):
  174. return None
  175. try:
  176. return value[node['value']]
  177. except IndexError:
  178. return None
  179. def visit_index_expression(self, node, value):
  180. result = value
  181. for node in node['children']:
  182. result = self.visit(node, result)
  183. return result
  184. def visit_slice(self, node, value):
  185. if not isinstance(value, list):
  186. return None
  187. s = slice(*node['children'])
  188. return value[s]
  189. def visit_key_val_pair(self, node, value):
  190. return self.visit(node['children'][0], value)
  191. def visit_literal(self, node, value):
  192. return node['value']
  193. def visit_multi_select_dict(self, node, value):
  194. if value is None:
  195. return None
  196. collected = self._dict_cls()
  197. for child in node['children']:
  198. collected[child['value']] = self.visit(child, value)
  199. return collected
  200. def visit_multi_select_list(self, node, value):
  201. if value is None:
  202. return None
  203. collected = []
  204. for child in node['children']:
  205. collected.append(self.visit(child, value))
  206. return collected
  207. def visit_or_expression(self, node, value):
  208. matched = self.visit(node['children'][0], value)
  209. if self._is_false(matched):
  210. matched = self.visit(node['children'][1], value)
  211. return matched
  212. def visit_and_expression(self, node, value):
  213. matched = self.visit(node['children'][0], value)
  214. if self._is_false(matched):
  215. return matched
  216. return self.visit(node['children'][1], value)
  217. def visit_not_expression(self, node, value):
  218. original_result = self.visit(node['children'][0], value)
  219. if original_result is 0:
  220. # Special case for 0, !0 should be false, not true.
  221. # 0 is not a special cased integer in jmespath.
  222. return False
  223. return not original_result
  224. def visit_pipe(self, node, value):
  225. result = value
  226. for node in node['children']:
  227. result = self.visit(node, result)
  228. return result
  229. def visit_projection(self, node, value):
  230. base = self.visit(node['children'][0], value)
  231. if not isinstance(base, list):
  232. return None
  233. collected = []
  234. for element in base:
  235. current = self.visit(node['children'][1], element)
  236. if current is not None:
  237. collected.append(current)
  238. return collected
  239. def visit_value_projection(self, node, value):
  240. base = self.visit(node['children'][0], value)
  241. try:
  242. base = base.values()
  243. except AttributeError:
  244. return None
  245. collected = []
  246. for element in base:
  247. current = self.visit(node['children'][1], element)
  248. if current is not None:
  249. collected.append(current)
  250. return collected
  251. def _is_false(self, value):
  252. # This looks weird, but we're explicitly using equality checks
  253. # because the truth/false values are different between
  254. # python and jmespath.
  255. return (value == '' or value == [] or value == {} or value is None or
  256. value is False)
  257. def _is_true(self, value):
  258. return not self._is_false(value)
  259. class GraphvizVisitor(Visitor):
  260. def __init__(self):
  261. super(GraphvizVisitor, self).__init__()
  262. self._lines = []
  263. self._count = 1
  264. def visit(self, node, *args, **kwargs):
  265. self._lines.append('digraph AST {')
  266. current = '%s%s' % (node['type'], self._count)
  267. self._count += 1
  268. self._visit(node, current)
  269. self._lines.append('}')
  270. return '\n'.join(self._lines)
  271. def _visit(self, node, current):
  272. self._lines.append('%s [label="%s(%s)"]' % (
  273. current, node['type'], node.get('value', '')))
  274. for child in node.get('children', []):
  275. child_name = '%s%s' % (child['type'], self._count)
  276. self._count += 1
  277. self._lines.append(' %s -> %s' % (current, child_name))
  278. self._visit(child, child_name)