From 8fce0139dc37648bb7db96c2a341d9ccd05ccb95 Mon Sep 17 00:00:00 2001 From: TANIGUCHI Takaki <takaki@debian.org> Date: Sat, 30 May 2020 13:54:50 +0900 Subject: [PATCH] New upstream version 0.10.0 --- PKG-INFO | 15 +- README.rst | 10 + bin/jp.py | 2 +- jmespath.egg-info/PKG-INFO | 15 +- jmespath.egg-info/SOURCES.txt | 5 +- jmespath/__init__.py | 13 +- jmespath/parser.py | 2 +- setup.py | 14 +- tests/__init__.py | 40 ---- tests/test_compliance.py | 109 ---------- tests/test_parser.py | 368 ---------------------------------- 11 files changed, 64 insertions(+), 529 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/test_compliance.py delete mode 100644 tests/test_parser.py diff --git a/PKG-INFO b/PKG-INFO index 8e567b4..6432a30 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: jmespath -Version: 0.9.5 +Version: 0.10.0 Summary: JSON Matching Expressions Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie @@ -55,6 +55,16 @@ Description: JMESPath The expression: ``foo.*.name`` will return ["one", "two"]. + Installation + ============ + + You can install JMESPath from pypi with: + + .. code:: bash + + pip install jmespath + + API === @@ -236,3 +246,4 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* diff --git a/README.rst b/README.rst index 3e972da..530709e 100644 --- a/README.rst +++ b/README.rst @@ -47,6 +47,16 @@ The ``*`` can also be used for hash types:: The expression: ``foo.*.name`` will return ["one", "two"]. +Installation +============ + +You can install JMESPath from pypi with: + +.. code:: bash + + pip install jmespath + + API === diff --git a/bin/jp.py b/bin/jp.py index 91b5ca9..6384876 100755 --- a/bin/jp.py +++ b/bin/jp.py @@ -34,7 +34,7 @@ def main(): data = json.loads(data) try: sys.stdout.write(json.dumps( - jmespath.search(expression, data), indent=4)) + jmespath.search(expression, data), indent=4, ensure_ascii=False)) sys.stdout.write('\n') except exceptions.ArityError as e: sys.stderr.write("invalid-arity: %s\n" % e) diff --git a/jmespath.egg-info/PKG-INFO b/jmespath.egg-info/PKG-INFO index 8e567b4..6432a30 100644 --- a/jmespath.egg-info/PKG-INFO +++ b/jmespath.egg-info/PKG-INFO @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: jmespath -Version: 0.9.5 +Version: 0.10.0 Summary: JSON Matching Expressions Home-page: https://github.com/jmespath/jmespath.py Author: James Saryerwinnie @@ -55,6 +55,16 @@ Description: JMESPath The expression: ``foo.*.name`` will return ["one", "two"]. + Installation + ============ + + You can install JMESPath from pypi with: + + .. code:: bash + + pip install jmespath + + API === @@ -236,3 +246,4 @@ Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* diff --git a/jmespath.egg-info/SOURCES.txt b/jmespath.egg-info/SOURCES.txt index 24375d4..ef526cc 100644 --- a/jmespath.egg-info/SOURCES.txt +++ b/jmespath.egg-info/SOURCES.txt @@ -15,7 +15,4 @@ jmespath/visitor.py jmespath.egg-info/PKG-INFO jmespath.egg-info/SOURCES.txt jmespath.egg-info/dependency_links.txt -jmespath.egg-info/top_level.txt -tests/__init__.py -tests/test_compliance.py -tests/test_parser.py \ No newline at end of file +jmespath.egg-info/top_level.txt \ No newline at end of file diff --git a/jmespath/__init__.py b/jmespath/__init__.py index f8f514b..99482db 100644 --- a/jmespath/__init__.py +++ b/jmespath/__init__.py @@ -1,7 +1,18 @@ +import warnings +import sys from jmespath import parser from jmespath.visitor import Options -__version__ = '0.9.5' +__version__ = '0.10.0' + + +if sys.version_info[:2] <= (2, 6) or ((3, 0) <= sys.version_info[:2] <= (3, 3)): + python_ver = '.'.join(str(x) for x in sys.version_info[:3]) + + warnings.warn( + 'You are using Python {0}, which will no longer be supported in ' + 'version 0.11.0'.format(python_ver), + DeprecationWarning) def compile(expression): diff --git a/jmespath/parser.py b/jmespath/parser.py index 4d5ba38..eeac38f 100644 --- a/jmespath/parser.py +++ b/jmespath/parser.py @@ -490,7 +490,7 @@ class Parser(object): def _free_cache_entries(self): for key in random.sample(self._CACHE.keys(), int(self._MAX_SIZE / 2)): - del self._CACHE[key] + self._CACHE.pop(key, None) @classmethod def purge(cls): diff --git a/setup.py b/setup.py index e7f0e95..3337d51 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,24 @@ #!/usr/bin/env python import io +import sys +import warnings from setuptools import setup, find_packages +if sys.version_info[:2] <= (2, 6) or ((3, 0) <= sys.version_info[:2] <= (3, 3)): + python_ver = '.'.join(str(x) for x in sys.version_info[:3]) + + warnings.warn( + 'You are using Python {0}, which will no longer be supported in ' + 'version 0.11.0'.format(python_ver), + DeprecationWarning) + + setup( name='jmespath', - version='0.9.5', + version='0.10.0', description='JSON Matching Expressions', long_description=io.open('README.rst', encoding='utf-8').read(), author='James Saryerwinnie', @@ -16,6 +27,7 @@ setup( scripts=['bin/jp.py'], packages=find_packages(exclude=['tests']), license='MIT', + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index d86946c..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -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 --git a/tests/test_compliance.py b/tests/test_compliance.py deleted file mode 100644 index 3cc8cdb..0000000 --- a/tests/test_compliance.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -from pprint import pformat -from tests import OrderedDict -from tests import json - -from nose.tools import assert_equal - -from jmespath.visitor import 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() -OPTIONS = Options(dict_cls=OrderedDict) - - -def test_compliance(): - for full_path in _walk_files(): - if full_path.endswith('.json'): - for given, test_type, test_data in load_cases(full_path): - t = test_data - # Benchmark tests aren't run as part of the normal - # test suite, so we only care about 'result' and - # 'error' test_types. - if test_type == 'result': - yield (_test_expression, given, t['expression'], - t['result'], os.path.basename(full_path)) - elif test_type == 'error': - yield (_test_error_expression, given, t['expression'], - t['error'], os.path.basename(full_path)) - - -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']: - if 'result' in case: - test_type = 'result' - elif 'error' in case: - test_type = 'error' - elif 'bench' in case: - test_type = 'bench' - else: - raise RuntimeError("Unknown test type: %s" % json.dumps(case)) - yield (given, test_type, case) - - -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, 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, pformat(parsed.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: - # Test passes, it raised a parse error as expected. - pass - except Exception as e: - # Failure because an unexpected exception was raised. - error_msg = ("\n\n (%s) The expression '%s' was suppose to be a " - "syntax error, but it raised an unexpected error:\n\n%s" % ( - filename, expression, e)) - error_msg = error_msg.replace(r'\n', '\n') - raise AssertionError(error_msg) - 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, pformat(parsed.parsed))) - error_msg = error_msg.replace(r'\n', '\n') - raise AssertionError(error_msg) diff --git a/tests/test_parser.py b/tests/test_parser.py deleted file mode 100644 index 9d6f43f..0000000 --- a/tests/test_parser.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env python - -import re -from tests import unittest, OrderedDict - -from jmespath import parser -from jmespath import visitor -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"}) - - 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): - - 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: ' - 'Unclosed " delimiter:\n' - 'foo."bar\n' - ' ^') - self.assert_error_message('foo."bar', 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 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() - 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() -- GitLab