Hello community, here is the log from the commit of package python-astor for openSUSE:Factory checked in at 2019-06-03 18:56:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-astor (Old) and /work/SRC/openSUSE:Factory/.python-astor.new.5148 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-astor" Mon Jun 3 18:56:38 2019 rev:2 rq:707079 version:0.8 Changes: -------- --- /work/SRC/openSUSE:Factory/python-astor/python-astor.changes 2018-11-12 09:41:47.529157517 +0100 +++ /work/SRC/openSUSE:Factory/.python-astor.new.5148/python-astor.changes 2019-06-03 18:56:40.876399680 +0200 @@ -1,0 +2,23 @@ +Mon Jun 3 08:18:07 UTC 2019 - pgaj...@suse.com + +- version update to 0.8 + * Support ``ast.Constant`` nodes being emitted by Python 3.8 (and initially + created in Python 3.6). + (Reported and fixed by Chris Rink in `Issue 120`_ and `PR 121`_.) + * Support Python 3.8's assignment expressions. + (Reported and fixed by Kodi Arfer in `Issue 126`_ and `PR 134`_.) + * Support Python 3.8's f-string debugging syntax. + (Reported and fixed by Batuhan Taskaya in `Issue 138`_ and `PR 139`_.) + * :func:`astor.to_source` now has a *source_generator_class* parameter to + customize source code generation. + (Reported and fixed by matham in `Issue 113`_ and `PR 114`_.) + * The :class:`~SourceGenerator` class can now be imported from the + :mod:`astor` package directly. Previously, the ``astor.code_gen`` + submodule was needed to be imported. + * Support Python 3.8's positional only arguments. See :pep:`570` for + more details. + (Reported and fixed by Batuhan Taskaya in `Issue 142`_ and `PR 143`_.) + * bug fixes, see changelog.rst +- run the testsuite + +------------------------------------------------------------------- Old: ---- astor-0.7.1.tar.gz New: ---- astor-0.8.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-astor.spec ++++++ --- /var/tmp/diff_new_pack.8ULWju/_old 2019-06-03 18:56:41.852399318 +0200 +++ /var/tmp/diff_new_pack.8ULWju/_new 2019-06-03 18:56:41.856399317 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-astor # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 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,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-astor -Version: 0.7.1 +Version: 0.8 Release: 0 Summary: Read/rewrite/write Python ASTs License: BSD-3-Clause @@ -27,6 +27,10 @@ Url: https://github.com/berkerpeksag/astor Source: https://github.com/berkerpeksag/astor/archive/%{version}.tar.gz#/astor-%{version}.tar.gz BuildRequires: %{python_module setuptools} +# SECTION test requirements +BuildRequires: %{python_module nose} +BuildRequires: python2-unittest2 +# /SECTION BuildRequires: fdupes BuildRequires: python-rpm-macros BuildArch: noarch @@ -70,8 +74,12 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} # fix executeable bits %python_expand chmod 755 %{buildroot}%{$python_sitelib}/astor/rtrip.py + +%check +%python_expand nosetests-%{$python_bin_suffix} -v + %files %{python_files} -%doc AUTHORS README.rst +%doc AUTHORS README.rst docs/*.rst %license LICENSE %{python_sitelib}/* ++++++ astor-0.7.1.tar.gz -> astor-0.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/.coveragerc new/astor-0.8/.coveragerc --- old/astor-0.7.1/.coveragerc 1970-01-01 01:00:00.000000000 +0100 +++ new/astor-0.8/.coveragerc 2019-05-19 18:51:56.000000000 +0200 @@ -0,0 +1,8 @@ +[run] +branch = True +omit = + # omit the codegen because of deprecation + astor/codegen.py + .tox/* +[report] +show_missing = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/.gitignore new/astor-0.8/.gitignore --- old/astor-0.7.1/.gitignore 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/.gitignore 2019-05-19 18:51:56.000000000 +0200 @@ -43,3 +43,5 @@ # Sphinx documentation docs/_build/ + +all_expr_*.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/.travis.yml new/astor-0.8/.travis.yml --- old/astor-0.7.1/.travis.yml 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/.travis.yml 2019-05-19 18:51:56.000000000 +0200 @@ -7,6 +7,16 @@ - 3.6 - pypy - pypy3.5 +matrix: + include: + # See https://github.com/travis-ci/travis-ci/issues/9069 + # for more information. + - python: 3.7 + sudo: required + dist: xenial + - python: 3.8-dev + sudo: required + dist: xenial cache: pip install: - pip install tox-travis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/AUTHORS new/astor-0.8/AUTHORS --- old/astor-0.7.1/AUTHORS 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/AUTHORS 2019-05-19 18:51:56.000000000 +0200 @@ -14,3 +14,6 @@ * Lenny Truong <leonardtru...@protonmail.com> * Radomír Bosák <radomir.bo...@gmail.com> * Kodi Arfer <g...@arfer.net> +* Felix Yan <felixonm...@archlinux.org> +* Chris Rink <chrisrin...@gmail.com> +* Batuhan Taskaya <batuhanosmantask...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/Makefile new/astor-0.8/Makefile --- old/astor-0.7.1/Makefile 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/Makefile 2019-05-19 18:51:56.000000000 +0200 @@ -5,19 +5,13 @@ python setup.py sdist bdist_wheel twine upload dist/* -register: - python setup.py sdist register -r pypi - # Test it via `pip install -i https://test.pypi.org/simple/ <project_name>` test-release: - python setup.py sdist bdist_wheel upload -r test - -test-register: - python setup.py sdist register -r test + twine upload -r test dist/* clean: find . -name "*.pyc" -exec rm {} \; rm -rf *.egg-info rm -rf build/ dist/ __pycache__/ -.PHONY: clean register release +.PHONY: clean release diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/__init__.py new/astor-0.8/astor/__init__.py --- old/astor-0.7.1/astor/__init__.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/__init__.py 2019-05-19 18:51:56.000000000 +0200 @@ -11,7 +11,7 @@ import warnings -from .code_gen import to_source # NOQA +from .code_gen import SourceGenerator, to_source # NOQA from .node_util import iter_node, strip_tree, dump_tree # NOQA from .node_util import ExplicitNodeVisitor # NOQA from .file_util import CodeToAst, code_to_ast # NOQA @@ -19,7 +19,7 @@ from .op_util import symbol_data # NOQA from .tree_walk import TreeWalk # NOQA -__version__ = '0.7.1' +__version__ = '0.8.0' parse_file = code_to_ast.parse_file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/code_gen.py new/astor-0.8/astor/code_gen.py --- old/astor-0.7.1/astor/code_gen.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/code_gen.py 2019-05-19 18:51:56.000000000 +0200 @@ -23,12 +23,13 @@ from .op_util import get_op_symbol, get_op_precedence, Precedence from .node_util import ExplicitNodeVisitor -from .string_repr import pretty_string, string_triplequote_repr +from .string_repr import pretty_string from .source_repr import pretty_source def to_source(node, indent_with=' ' * 4, add_line_information=False, - pretty_string=pretty_string, pretty_source=pretty_source): + pretty_string=pretty_string, pretty_source=pretty_source, + source_generator_class=None): """This function can convert a node tree back into python sourcecode. This is useful for debugging purposes, especially if you're dealing with custom asts not generated by python itself. @@ -46,9 +47,18 @@ of the nodes are added to the output. This can be used to spot wrong line number information of statement nodes. + `source_generator_class` defaults to `SourceGenerator`, and specifies the + class that will be instantiated and used to generate the source code. + """ - generator = SourceGenerator(indent_with, add_line_information, - pretty_string) + if source_generator_class is None: + source_generator_class = SourceGenerator + elif not isinstance(source_generator_class, SourceGenerator): + raise TypeError('source_generator_class should be a subclass of SourceGenerator') + elif not callable(source_generator_class): + raise TypeError('source_generator_class should be a callable') + generator = source_generator_class( + indent_with, add_line_information, pretty_string) generator.visit(node) generator.result.append('\n') if set(generator.result[0]) == set('\n'): @@ -155,7 +165,6 @@ AST = ast.AST visit = self.visit - newline = self.newline result = self.result append = result.append @@ -168,8 +177,6 @@ visit(item) elif callable(item): item() - elif item == '\n': - newline() else: if self.new_lines: append('\n' * self.new_lines) @@ -220,7 +227,7 @@ def else_body(self, elsewhat): if elsewhat: - self.write('\n', 'else:') + self.write(self.newline, 'else:') self.body(elsewhat) def body_or_else(self, node): @@ -243,7 +250,14 @@ self.write(write_comma, arg) self.conditional_write('=', default) - loop_args(node.args, node.defaults) + posonlyargs = getattr(node, 'posonlyargs', []) + offset = 0 + if posonlyargs: + offset += len(node.defaults) - len(node.args) + loop_args(posonlyargs, node.defaults[:offset]) + self.write(write_comma, '/') + + loop_args(node.args, node.defaults[offset:]) self.conditional_write(write_comma, '*', node.vararg) kwonlyargs = self.get_kwonlyargs(node) @@ -359,7 +373,7 @@ if len(else_) == 1 and isinstance(else_[0], ast.If): node = else_[0] set_precedence(node, node.test) - self.write('\n', 'elif ', node.test, ':') + self.write(self.newline, 'elif ', node.test, ':') self.body(node.body) else: self.else_body(else_) @@ -400,8 +414,9 @@ self.write(node.context_expr) self.conditional_write(' as ', node.optional_vars) + # deprecated in Python 3.8 def visit_NameConstant(self, node): - self.write(str(node.value)) + self.write(repr(node.value)) def visit_Pass(self, node): self.statement(node, 'pass') @@ -530,11 +545,25 @@ def visit_Name(self, node): self.write(node.id) - def visit_JoinedStr(self, node): - self.visit_Str(node, True) + # ast.Constant is new in Python 3.6 and it replaces ast.Bytes, + # ast.Ellipsis, ast.NameConstant, ast.Num, ast.Str in Python 3.8 + def visit_Constant(self, node): + value = node.value + + if isinstance(value, (int, float, complex)): + with self.delimit(node): + self._handle_numeric_constant(value) + elif isinstance(value, str): + self._handle_string_constant(node, node.value) + elif value is Ellipsis: + self.write('...') + else: + self.write(repr(value)) - def visit_Str(self, node, is_joined=False): + def visit_JoinedStr(self, node): + self._handle_string_constant(node, None, is_joined=True) + def _handle_string_constant(self, node, value, is_joined=False): # embedded is used to control when we might want # to use a triple-quoted string. We determine # if we are in an assignment and/or in an expression @@ -557,8 +586,9 @@ current_line[0] = current_line[0][str_index:] current_line = ''.join(current_line) - if is_joined: + has_ast_constant = sys.version_info >= (3, 6) + if is_joined: # Handle new f-strings. This is a bit complicated, because # the tree can contain subnodes that recurse back to JoinedStr # subnodes... @@ -566,15 +596,22 @@ def recurse(node): for value in node.values: if isinstance(value, ast.Str): - self.write(value.s) + # Double up braces to escape them. + self.write(value.s.replace('{', '{{').replace('}', '}}')) elif isinstance(value, ast.FormattedValue): with self.delimit('{}'): - self.visit(value.value) + # expr_text used for f-string debugging syntax. + if getattr(value, 'expr_text', None): + self.write(value.expr_text) + else: + self.visit(value.value) if value.conversion != -1: self.write('!%s' % chr(value.conversion)) if value.format_spec is not None: self.write(':') recurse(value.format_spec) + elif has_ast_constant and isinstance(value, ast.Constant): + self.write(value.value) else: kind = type(value).__name__ assert False, 'Invalid node %s inside JoinedStr' % kind @@ -590,13 +627,17 @@ uni_lit = False # No formatted byte strings else: - mystr = node.s + assert value is not None, "Node value cannot be None" + mystr = value uni_lit = self.using_unicode_literals mystr = self.pretty_string(mystr, embedded, current_line, uni_lit) if is_joined: mystr = 'f' + mystr + elif getattr(node, 'kind', False): + # Constant.kind is a Python 3.8 addition. + mystr = node.kind + mystr self.write(mystr) @@ -604,39 +645,56 @@ if lf: self.colinfo = len(result) - 1, lf + # deprecated in Python 3.8 + def visit_Str(self, node): + self._handle_string_constant(node, node.s) + + # deprecated in Python 3.8 def visit_Bytes(self, node): self.write(repr(node.s)) - def visit_Num(self, node, - # constants - new=sys.version_info >= (3, 0)): - with self.delimit(node) as delimiters: - x = node.n + def _handle_numeric_constant(self, value): + x = value - def part(p, imaginary): - # Represent infinity as 1e1000 and NaN as 1e1000-1e1000. - s = 'j' if imaginary else '' + def part(p, imaginary): + # Represent infinity as 1e1000 and NaN as 1e1000-1e1000. + s = 'j' if imaginary else '' + try: if math.isinf(p): if p < 0: return '-1e1000' + s return '1e1000' + s if math.isnan(p): return '(1e1000%s-1e1000%s)' % (s, s) - return repr(p) + s - - real = part(x.real if isinstance(x, complex) else x, imaginary=False) - if isinstance(x, complex): - imag = part(x.imag, imaginary=True) - if x.real == 0: - s = imag - elif x.imag == 0: - s = '(%s+0j)' % real - else: - # x has nonzero real and imaginary parts. - s = '(%s%s%s)' % (real, ['+', ''][imag.startswith('-')], imag) + except OverflowError: + # math.isinf will raise this when given an integer + # that's too large to convert to a float. + pass + return repr(p) + s + + real = part(x.real if isinstance(x, complex) else x, imaginary=False) + if isinstance(x, complex): + imag = part(x.imag, imaginary=True) + if x.real == 0: + s = imag + elif x.imag == 0: + s = '(%s+0j)' % real else: - s = real - self.write(s) + # x has nonzero real and imaginary parts. + s = '(%s%s%s)' % (real, ['+', ''][imag.startswith('-')], imag) + else: + s = real + self.write(s) + + def visit_Num(self, node, + # constants + new=sys.version_info >= (3, 0)): + with self.delimit(node) as delimiters: + self._handle_numeric_constant(node.n) + + # We can leave the delimiters handling in visit_Num + # since this is meant to handle a Python 2.x specific + # issue and ast.Constant exists only in 3.6+ # The Python 2.x compiler merges a unary minus # with a number. This is a premature optimization @@ -703,6 +761,22 @@ for op, right in zip(node.ops, node.comparators): self.write(get_op_symbol(op, ' %s '), right) + # assignment expressions; new for Python 3.8 + def visit_NamedExpr(self, node): + with self.delimit(node) as delimiters: + p = delimiters.p + set_precedence(p, node.target) + set_precedence(p + 1, node.value) + # Python is picky about delimiters for assignment + # expressions: it requires at least one pair in any + # statement that uses an assignment expression, even + # when not necessary according to the precedence + # rules. We address this with the kludge of forcing a + # pair of parentheses around every assignment + # expression. + delimiters.discard = False + self.write(node.target, ' := ', node.value) + def visit_UnaryOp(self, node): with self.delimit(node, node.op) as delimiters: set_precedence(delimiters.p, node.operand) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/op_util.py new/astor-0.8/astor/op_util.py --- old/astor-0.7.1/astor/op_util.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/op_util.py 2019-05-19 18:51:56.000000000 +0200 @@ -36,6 +36,7 @@ Tuple 0 Comma 1 + NamedExpr 1 Assert 0 Raise 0 call_one_arg 1 @@ -78,6 +79,7 @@ Pow ** 1 Await 1 Num 1 + Constant 1 """ op_data = [x.split() for x in op_data.splitlines()] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/rtrip.py new/astor-0.8/astor/rtrip.py --- old/astor-0.7.1/astor/rtrip.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/rtrip.py 2019-05-19 18:51:56.000000000 +0200 @@ -81,7 +81,7 @@ try: dsttxt = to_source(srcast) - except: + except Exception: if not ignore_exceptions: raise dsttxt = '' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/source_repr.py new/astor-0.8/astor/source_repr.py --- old/astor-0.7.1/astor/source_repr.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/source_repr.py 2019-05-19 18:51:56.000000000 +0200 @@ -71,7 +71,7 @@ indentation = line[0] lenfirst = len(indentation) - indent = lenfirst - len(indentation.strip()) + indent = lenfirst - len(indentation.lstrip()) assert indent in (0, lenfirst) indentation = line.pop(0) if indent else '' @@ -101,8 +101,8 @@ pos = indent + count(first) indentation += ' ' indent += 4 - if indent >= maxline/2: - maxline = maxline/2 + indent + if indent >= maxline / 2: + maxline = maxline / 2 + indent for sg, nsg in zip(splittable, unsplittable[1:]): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/string_repr.py new/astor-0.8/astor/string_repr.py --- old/astor-0.7.1/astor/string_repr.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/string_repr.py 2019-05-19 18:51:56.000000000 +0200 @@ -107,6 +107,6 @@ try: if eval(fancy) == s and '\r' not in fancy: return fancy - except: + except Exception: pass return default diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/astor/tree_walk.py new/astor-0.8/astor/tree_walk.py --- old/astor-0.7.1/astor/tree_walk.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/astor/tree_walk.py 2019-05-19 18:51:56.000000000 +0200 @@ -30,6 +30,9 @@ if base not in newbases: newdict.update(vars(base)) newdict.update(clsdict) + # These are class-bound, we should let Python recreate them. + newdict.pop('__dict__', None) + newdict.pop('__weakref__', None) # Delegate the real work to type return type.__new__(clstype, name, newbases, newdict) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/docs/changelog.rst new/astor-0.8/docs/changelog.rst --- old/astor-0.7.1/docs/changelog.rst 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/docs/changelog.rst 2019-05-19 18:51:56.000000000 +0200 @@ -2,18 +2,81 @@ Release Notes ============= -0.8.0 - in development ----------------------- +0.8.0 - 2019-05-19 +------------------ New features ~~~~~~~~~~~~ -* +* Support ``ast.Constant`` nodes being emitted by Python 3.8 (and initially + created in Python 3.6). + (Reported and fixed by Chris Rink in `Issue 120`_ and `PR 121`_.) + +.. _`Issue 120`: https://github.com/berkerpeksag/astor/issues/120 +.. _`PR 121`: https://github.com/berkerpeksag/astor/pull/121 + +* Support Python 3.8's assignment expressions. + (Reported and fixed by Kodi Arfer in `Issue 126`_ and `PR 134`_.) + +.. _`Issue 126`: https://github.com/berkerpeksag/astor/issues/126 +.. _`PR 134`: https://github.com/berkerpeksag/astor/pull/134 + +* Support Python 3.8's f-string debugging syntax. + (Reported and fixed by Batuhan Taskaya in `Issue 138`_ and `PR 139`_.) + +.. _`Issue 138`: https://github.com/berkerpeksag/astor/issues/138 +.. _`PR 139`: https://github.com/berkerpeksag/astor/pull/139 + +* :func:`astor.to_source` now has a *source_generator_class* parameter to + customize source code generation. + (Reported and fixed by matham in `Issue 113`_ and `PR 114`_.) + +.. _`Issue 113`: https://github.com/berkerpeksag/astor/issues/113 +.. _`PR 114`: https://github.com/berkerpeksag/astor/pull/114 + +* The :class:`~SourceGenerator` class can now be imported from the + :mod:`astor` package directly. Previously, the ``astor.code_gen`` + submodule was needed to be imported. + +* Support Python 3.8's positional only arguments. See :pep:`570` for + more details. + (Reported and fixed by Batuhan Taskaya in `Issue 142`_ and `PR 143`_.) + +.. _`Issue 142`: https://github.com/berkerpeksag/astor/issues/142 +.. _`PR 143`: https://github.com/berkerpeksag/astor/pull/143 Bug fixes ~~~~~~~~~ -* +* Fix string parsing when there is a newline inside an f-string. (Reported by + Adam Cécile in `Issue 119`_ and fixed by Felix Yan in `PR 123`_.) + +* Fixed code generation with escaped braces in f-strings. + (Reported by Felix Yan in `Issue 124`_ and fixed by Kodi Arfer in `PR 125`_.) + +.. _`Issue 119`: https://github.com/berkerpeksag/astor/issues/119 +.. _`PR 123`: https://github.com/berkerpeksag/astor/pull/123 +.. _`Issue 124`: https://github.com/berkerpeksag/astor/issues/124 +.. _`PR 125`: https://github.com/berkerpeksag/astor/pull/125 + +* Fixed code generation with attributes of integer literals, and + with ``u``-prefixed string literals. + (Fixed by Kodi Arfer in `PR 133`_.) + +.. _`PR 133`: https://github.com/berkerpeksag/astor/pull/133 + +* Fixed code generation with very large integers. + (Reported by Adam Kucz in `Issue 127`_ and fixed by Kodi Arfer in `PR 130`_.) + +.. _`Issue 127`: https://github.com/berkerpeksag/astor/issues/127 +.. _`PR 130`: https://github.com/berkerpeksag/astor/pull/130 + +* Fixed :class:`astor.tree_walk.TreeWalk` when attempting to access attributes + created by Python's type system (such as ``__dict__`` and ``__weakref__``) + (Reported and fixed by esupoff in `Issue 136`_ and `PR 137`_.) + +.. _`Issue 136`: https://github.com/berkerpeksag/astor/issues/136 +.. _`PR 137`: https://github.com/berkerpeksag/astor/pull/137 0.7.1 - 2018-07-06 ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/docs/index.rst new/astor-0.8/docs/index.rst --- old/astor-0.7.1/docs/index.rst 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/docs/index.rst 2019-05-19 18:51:56.000000000 +0200 @@ -129,7 +129,8 @@ ********* .. function:: to_source(source, indent_with=' ' * 4, \ - add_line_information=False) + add_line_information=False, + source_generator_class=astor.SourceGenerator) Convert a node tree back into Python source code. @@ -140,6 +141,13 @@ of the nodes are added to the output. This can be used to spot wrong line number information of statement nodes. + *source_generator_class* defaults to :class:`astor.SourceGenerator`, and + specifies the class that will be instantiated and used to generate the + source code. + + .. versionchanged:: 0.8 + *source_generator_class* was added. + .. function:: codetoast .. function:: code_to_ast(codeobj) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/requirements-tox.txt new/astor-0.8/requirements-tox.txt --- old/astor-0.7.1/requirements-tox.txt 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/requirements-tox.txt 2019-05-19 18:51:56.000000000 +0200 @@ -1 +1,3 @@ nose>=1.3.0 +flake8>=3.7.0 +coverage>=4.5.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/setup.cfg new/astor-0.8/setup.cfg --- old/astor-0.7.1/setup.cfg 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/setup.cfg 2019-05-19 18:51:56.000000000 +0200 @@ -3,7 +3,7 @@ description = Read/rewrite/write Python ASTs long_description = file:README.rst author = Patrick Maupin -email = pmau...@gmail.com +author_email = pmau...@gmail.com platforms = Independent url = https://github.com/berkerpeksag/astor license = BSD-3-Clause @@ -16,14 +16,13 @@ Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 - Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 Programming Language :: Python :: Implementation Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy @@ -35,6 +34,8 @@ include_package_data = True packages = find: python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +tests_requires = ["nose", "astunparse"] +test_suite = nose.collector [options.packages.find] exclude = tests diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/tests/check_astunparse.py new/astor-0.8/tests/check_astunparse.py --- old/astor-0.7.1/tests/check_astunparse.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/tests/check_astunparse.py 2019-05-19 18:51:56.000000000 +0200 @@ -5,7 +5,7 @@ except ImportError: import unittest -import test_code_gen +from . import test_code_gen import astunparse diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/tests/check_expressions.py new/astor-0.8/tests/check_expressions.py --- old/astor-0.7.1/tests/check_expressions.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/tests/check_expressions.py 2019-05-19 18:51:56.000000000 +0200 @@ -33,7 +33,7 @@ import all_expr_2_6 as mymod except ImportError: print("Expression list does not exist -- building") - import build_expressions + from . import build_expressions build_expressions.makelib() print("Expression list built") import all_expr_2_6 as mymod @@ -44,7 +44,7 @@ mymod = importlib.import_module(mymodname) except ImportError: print("Expression list does not exist -- building") - import build_expressions + from . import build_expressions build_expressions.makelib() print("Expression list built") mymod = importlib.import_module(mymodname) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/tests/test_code_gen.py new/astor-0.8/tests/test_code_gen.py --- old/astor-0.7.1/tests/test_code_gen.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/tests/test_code_gen.py 2019-05-19 18:51:56.000000000 +0200 @@ -1,3 +1,4 @@ +# coding: utf-8 """ Part of the astor library for Python AST manipulation @@ -38,6 +39,9 @@ dmp2 = astor.dump_tree(ast2) self.assertEqual(dmp1, dmp2) + def assertAstEqualsSource(self, tree, source): + self.assertEqual(self.to_source(tree).rstrip(), source) + def assertAstRoundtrips(self, srctxt): """This asserts that the reconstituted source code can be compiled into the exact same AST @@ -160,6 +164,21 @@ pass""" self.assertSrcRoundtrips(source) + @unittest.skipUnless(sys.version_info >= (3, 8, 0, "alpha", 4), + "positional only arguments introduced in Python 3.8") + def test_positional_only_arguments(self): + source = """ + def test(a, b, /, c, *, d, **kwargs): + pass + + def test(a=3, b=4, /, c=7): + pass + + def test(a, b=4, /, c=8, d=9): + pass + """ + self.assertSrcRoundtrips(source) + def test_pass_arguments_node(self): source = canonical(""" j = [1, 2, 3] @@ -178,6 +197,10 @@ # Probably also works on < 3.4, but doesn't work on 2.7... self.assertSrcRoundtripsGtVer(source, (3, 4), (2, 7)) + def test_attribute(self): + self.assertSrcRoundtrips("x.foo") + self.assertSrcRoundtrips("(5).foo") + def test_matrix_multiplication(self): for source in ("(a @ b)", "a @= b"): self.assertAstRoundtripsGtVer(source, (3, 5)) @@ -259,6 +282,11 @@ """ self.assertAstRoundtripsGtVer(source, (2, 7)) + def test_huge_int(self): + for n in (10**10000, + 0xdfa21cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccee254f371bcad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b): + self.assertEqual(astornum(n), n) + def test_complex(self): source = """ (3) + (4j) + (1+2j) + (1+0j) @@ -442,6 +470,126 @@ """ self.assertSrcRoundtripsGtVer(source, (3, 6)) + def test_assignment_expr(self): + cases = ( + "(x := 3)", + "1 + (x := y)", + "x = (y := 0)", + "1 + (p := 1 if 2 else 3)", + "[y := f(x), y**2, y**3]", + "(2 ** 3 * 4 + 5 and 6, x := 2 ** 3 * 4 + 5 and 6)", + "foo(x := 3, cat='vector')", + "foo(x=(y := f(x)))", + "any(len(longline := line) >= 100 for line in lines)", + "[[y := f(x), x/y] for x in range(5)]", + "lambda: (x := 1)", + "def foo(answer=(p := 42)): pass", + "def foo(answer: (p := 42) = 5): pass", + "if reductor := dispatch_table.get(cls): pass", + "while line := fp.readline(): pass", + "while (command := input('> ')) != 'quit': pass") + for case in cases: + self.assertAstRoundtripsGtVer(case, (3, 8)) + + @unittest.skipUnless(sys.version_info <= (3, 3), + "ast.Name used for True, False, None until Python 3.4") + def test_deprecated_constants_as_name(self): + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Name(id='True')), + "spam = True") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Name(id='False')), + "spam = False") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Name(id='None')), + "spam = None") + + @unittest.skipUnless(sys.version_info >= (3, 4), + "ast.NameConstant introduced in Python 3.4") + def test_deprecated_name_constants(self): + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.NameConstant(value=True)), + "spam = True") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.NameConstant(value=False)), + "spam = False") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.NameConstant(value=None)), + "spam = None") + + def test_deprecated_constant_nodes(self): + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Num(3)), + "spam = 3") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Num(-93)), + "spam = -93") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Num(837.3888)), + "spam = 837.3888") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Num(-0.9877)), + "spam = -0.9877") + + self.assertAstEqualsSource(ast.Ellipsis(), "...") + + if sys.version_info >= (3, 0): + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Bytes(b"Bytes")), + "spam = b'Bytes'") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Str("String")), + "spam = 'String'") + + @unittest.skipUnless(sys.version_info >= (3, 6), + "ast.Constant introduced in Python 3.6") + def test_constant_nodes(self): + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=3)), + "spam = 3") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=-93)), + "spam = -93") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=837.3888)), + "spam = 837.3888") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=-0.9877)), + "spam = -0.9877") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=True)), + "spam = True") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=False)), + "spam = False") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value=None)), + "spam = None") + + self.assertAstEqualsSource(ast.Constant(value=Ellipsis), "...") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(b"Bytes")), + "spam = b'Bytes'") + + self.assertAstEqualsSource( + ast.Assign(targets=[ast.Name(id='spam')], value=ast.Constant(value="String")), + "spam = 'String'") + def test_annassign(self): source = """ a: int @@ -516,6 +664,34 @@ x = f"""{host}\n\t{port}\n""" ''' self.assertSrcRoundtripsGtVer(source, (3, 6)) + source = ''' + if 1: + x = f'{host}\\n\\t{port}\\n' + ''' + self.assertSrcRoundtripsGtVer(source, (3, 6)) + + def test_fstring_escaped_braces(self): + source = ''' + x = f'{{hello world}}' + ''' + self.assertSrcRoundtripsGtVer(source, (3, 6)) + source = ''' + x = f'{f.name}={{self.{f.name}!r}}' + ''' + self.assertSrcRoundtripsGtVer(source, (3, 6)) + + @unittest.skipUnless(sys.version_info >= (3, 8, 0, "alpha", 4), + "f-string debugging introduced in Python 3.8") + def test_fstring_debugging(self): + source = """ + x = f'{5=}' + y = f'{5=!r}' + z = f'{3*x+15=}' + f'{x=:}' + f'{x=:.2f}' + f'alpha α {pi=} ω omega' + """ + self.assertAstRoundtripsGtVer(source, (3, 8)) def test_docstring_function(self): source = ''' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/tests/test_misc.py new/astor-0.8/tests/test_misc.py --- old/astor-0.7.1/tests/test_misc.py 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/tests/test_misc.py 2019-05-19 18:51:56.000000000 +0200 @@ -46,6 +46,29 @@ 'astor.code_gen module instead.' ) + def test_to_source_invalid_customize_generator(self): + class InvalidGenerator: + pass + + node = ast.parse('spam = 42') + + with self.assertRaises(TypeError) as cm: + astor.to_source(node, source_generator_class=InvalidGenerator) + self.assertEqual( + str(cm.exception), + 'source_generator_class should be a subclass of SourceGenerator', + ) + + with self.assertRaises(TypeError) as cm: + astor.to_source( + node, + source_generator_class=astor.SourceGenerator(indent_with=' ' * 4), + ) + self.assertEqual( + str(cm.exception), + 'source_generator_class should be a callable', + ) + class FastCompareTestCase(unittest.TestCase): @@ -64,5 +87,17 @@ check('a = 3 - (3, 4, 5)', 'a = 3 - (3, 4, 6)') +class TreeWalkTestCase(unittest.TestCase): + + def test_auto_generated_attributes(self): + # See #136 for more details. + treewalk = astor.TreeWalk() + self.assertIsInstance(treewalk.__dict__, dict) + # Check that the inital state of the instance is empty. + self.assertEqual(treewalk.__dict__['nodestack'], []) + self.assertEqual(treewalk.__dict__['pre_handlers'], {}) + self.assertEqual(treewalk.__dict__['post_handlers'], {}) + + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/astor-0.7.1/tox.ini new/astor-0.8/tox.ini --- old/astor-0.7.1/tox.ini 2018-07-06 10:03:51.000000000 +0200 +++ new/astor-0.8/tox.ini 2019-05-19 18:51:56.000000000 +0200 @@ -1,10 +1,26 @@ [tox] -envlist = py27, py34, py35, py36, pypy, pypy3.5 +envlist = + py{27, 34, 35, 36, 37, 38, py, py3.5} + lint skipsdist = True +skip_missing_interpreters = true [testenv] usedevelop = True -commands = nosetests -v --nocapture {posargs} +commands = + coverage run {envbindir}/nosetests -v --nocapture {posargs} + coverage report deps = -rrequirements-tox.txt py27,pypy: unittest2 + +[testenv:lint] +deps = flake8 +commands = flake8 astor/ + +[flake8] +ignore = E114, E116, E501, W504 + +[travis] +python = + 3.7: py37, lint