From 7fcca10114e014db48ec3e229d7940fe2a0ba0ad Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Wed, 1 Jan 2025 22:35:03 +0200 Subject: [PATCH] Drop Python 2 support Closes #179 --- README.rst | 8 +------ astor/code_gen.py | 49 +++--------------------------------------- astor/file_util.py | 11 ++-------- astor/string_repr.py | 20 +++-------------- tests/test_code_gen.py | 7 +++--- tests/test_misc.py | 13 ----------- 6 files changed, 12 insertions(+), 96 deletions(-) diff --git a/README.rst b/README.rst index c8b9de5..a52760b 100644 --- a/README.rst +++ b/README.rst @@ -52,10 +52,4 @@ There are some other similar libraries, but astor focuses on the following areas Other derivatives of Armin's code are floating around, and typically have fixes for a few corner cases that happened to be noticed by the maintainers, but most of them have not been tested as thoroughly as - astor. One exception may be the version of codegen - `maintained at github by CensoredUsername`__. This has been tested - to work properly on Python 2.7 using astor's test suite, and, as it - is a single source file, it may be easier to drop into some applications - that do not require astor's other features or Python 3.x compatibility. - -__ https://github.com/CensoredUsername/codegen + astor. diff --git a/astor/code_gen.py b/astor/code_gen.py index ff00f22..39b6114 100644 --- a/astor/code_gen.py +++ b/astor/code_gen.py @@ -149,8 +149,6 @@ class SourceGenerator(ExplicitNodeVisitor): """ - using_unicode_literals = False - def __init__(self, indent_with, add_line_information=False, pretty_string=pretty_string, # constants @@ -321,10 +319,6 @@ def visit_ImportFrom(self, node): self.statement(node, 'from ', node.level * '.', node.module or '', ' import ') self.comma_list(node.names) - # Goofy stuff for Python 2.7 _pyio module - if node.module == '__future__' and 'unicode_literals' in ( - x.name for x in node.names): - self.using_unicode_literals = True def visit_Import(self, node): self.statement(node, 'import ') @@ -455,15 +449,6 @@ def visit_NameConstant(self, node): def visit_Pass(self, node): self.statement(node, 'pass') - def visit_Print(self, node): - # XXX: python 2.6 only - self.statement(node, 'print ') - values = node.values - if node.dest is not None: - self.write(' >> ') - values = [node.dest] + node.values - self.comma_list(values, not node.nl) - def visit_Delete(self, node): self.statement(node, 'del ') self.comma_list(node.targets) @@ -540,14 +525,9 @@ def visit_Continue(self, node): self.statement(node, 'continue') def visit_Raise(self, node): - # XXX: Python 2.6 / 3.0 compatibility self.statement(node, 'raise') if self.conditional_write(' ', self.get_exc(node)): self.conditional_write(' from ', node.cause) - elif self.conditional_write(' ', self.get_type(node)): - set_precedence(node, node.inst) - self.conditional_write(', ', node.inst) - self.conditional_write(', ', node.tback) # Match statement (introduced in Python 3.10) def visit_Match(self, node): @@ -742,14 +722,12 @@ def recurse(node): mystr = ''.join(result[index:]) del result[index:] self.colinfo = res_index, str_index # Put it back like we found it - uni_lit = False # No formatted byte strings else: 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) + mystr = self.pretty_string(mystr, embedded, current_line) if is_joined: mystr = 'f' + mystr @@ -804,27 +782,10 @@ def part(p, imaginary): s = real self.write(s) - def visit_Num(self, node, - # constants - new=sys.version_info >= (3, 0)): + def visit_Num(self, node): 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 - # that we deal with here... - if not new and delimiters.discard: - if not isinstance(node.n, complex) and node.n < 0: - pow_lhs = Precedence.Pow + 1 - delimiters.discard = delimiters.pp != pow_lhs - else: - op = self.get__p_op(node) - delimiters.discard = not isinstance(op, ast.USub) - def visit_Tuple(self, node): with self.delimit(node) as delimiters: # Two things are special about tuples: @@ -902,6 +863,7 @@ def visit_NamedExpr(self, node): def visit_UnaryOp(self, node): with self.delimit(node, node.op) as delimiters: set_precedence(delimiters.p, node.operand) + # TODO: Remove this. # In Python 2.x, a unary negative of a literal # number is merged into the number itself. This # bit of ugliness means it is useful to know @@ -989,11 +951,6 @@ def visit_IfExp(self, node): def visit_Starred(self, node): self.write('*', node.value) - def visit_Repr(self, node): - # XXX: python 2.6 only - with self.delimit('``'): - self.visit(node.value) - def visit_Module(self, node): self.write(*node.body) diff --git a/astor/file_util.py b/astor/file_util.py index ee5b9f3..ec91516 100644 --- a/astor/file_util.py +++ b/astor/file_util.py @@ -13,13 +13,9 @@ import ast import sys +import tokenize import os -try: - from tokenize import open as fopen -except ImportError: - fopen = open - class CodeToAst(object): """Given a module, or a function that was compiled as part @@ -56,12 +52,9 @@ def parse_file(fname): """Parse a python file into an AST. This is a very thin wrapper around ast.parse - - TODO: Handle encodings other than the default for Python 2 - (issue #26) """ try: - with fopen(fname) as f: + with tokenize.open(fname) as f: fstr = f.read() except IOError: if fname != 'stdin': diff --git a/astor/string_repr.py b/astor/string_repr.py index 6dce9cc..191cbca 100644 --- a/astor/string_repr.py +++ b/astor/string_repr.py @@ -13,23 +13,10 @@ This is a lot harder than you would think. -This has lots of Python 2 / Python 3 ugliness. - """ import re -try: - special_unicode = unicode -except NameError: - class special_unicode(object): - pass - -try: - basestring = basestring -except NameError: - basestring = str - def _properly_indented(s, line_indent): mylist = s.split('\n')[1:] @@ -61,8 +48,7 @@ def string_triplequote_repr(s): return '"""%s"""' % _prep_triple_quotes(s) -def pretty_string(s, embedded, current_line, uni_lit=False, - min_trip_str=20, max_line=100): +def pretty_string(s, embedded, current_line, min_trip_str=20, max_line=100): """There are a lot of reasons why we might not want to or be able to return a triple-quoted string. We can always punt back to the default normal string. @@ -71,9 +57,9 @@ def pretty_string(s, embedded, current_line, uni_lit=False, default = repr(s) # Punt on abnormal strings - if (isinstance(s, special_unicode) or not isinstance(s, basestring)): + if isinstance(s, unicode): return default - if uni_lit and isinstance(s, bytes): + if isinstance(s, bytes): return 'b' + default len_s = len(default) diff --git a/tests/test_code_gen.py b/tests/test_code_gen.py index 0ab53e1..04d7511 100644 --- a/tests/test_code_gen.py +++ b/tests/test_code_gen.py @@ -571,10 +571,9 @@ def test_deprecated_constant_nodes(self): 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.Bytes(b"Bytes")), + "spam = b'Bytes'") self.assertAstEqualsSource( ast.Assign(targets=[ast.Name(id='spam')], value=ast.Str("String")), diff --git a/tests/test_misc.py b/tests/test_misc.py index 4e742e9..d81b635 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -101,18 +101,5 @@ def test_auto_generated_attributes(self): self.assertEqual(treewalk.__dict__['post_handlers'], {}) -class SourceReprTestCase(unittest.TestCase): - """ - Tests for helpers in astor.source_repr module. - - Note that these APIs are not public. - """ - - @unittest.skipUnless(sys.version_info[0] == 2, 'only applies to Python 2') - def test_split_lines_unicode_support(self): - source = [u'copy', '\n'] - self.assertEqual(split_lines(source), source) - - if __name__ == '__main__': unittest.main()