Skip to content

Commit

Permalink
Merge pull request #257 from aktech/drop-py37
Browse files Browse the repository at this point in the history
Drop support for Python 3.7
  • Loading branch information
Carreau authored Sep 1, 2023
2 parents 093df34 + ee68a3e commit 288b28e
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 166 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version:
- "3.8"
- "3.9"
- "3.10"
include:
- os: macos-latest
python-version: "3.10"
Expand All @@ -36,6 +39,7 @@ jobs:
- name: compileall
run: |
python -We:invalid -m compileall -f -q lib/ etc/;
- name: pytest Mac OS
if: ${{ matrix.os == 'macos-latest'}}
# json report can't be installed on Py2, and make macos super slow.
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ Compatibility
-------------

Tested with:
- Python 3.7, 3.8, 3.9, 3.10
- Python 3.8, 3.9, 3.10
- IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0,
3.1, 3.2, 4.0., 7.11 (latest)
- IPython (text console), IPython Notebook, Spyder
Expand Down
27 changes: 7 additions & 20 deletions lib/python/pyflyby/_autoimp.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,17 +609,13 @@ def visit_FunctionDef(self, node):
# scope.
# - Store the name in the current scope (but not visibly to
# args/decorator_list).
if sys.version_info >= (3, 8):
assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
else:
assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns'), node._fields
assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
with self._NewScopeCtx(include_class_scopes=True):
self.visit(node.args)
self.visit(node.decorator_list)
if node.returns:
self.visit(node.returns)
if sys.version_info >= (3, 8):
self._visit_typecomment(node.type_comment)
self._visit_typecomment(node.type_comment)
old_in_FunctionDef = self._in_FunctionDef
self._in_FunctionDef = True
with self._NewScopeCtx(unhide_classdef=True):
Expand Down Expand Up @@ -671,10 +667,7 @@ def foo(a #type: int
self.visit(node)

def visit_arguments(self, node):
if sys.version_info >= (3, 8):
assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
else:
assert node._fields == ('args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
# Argument/parameter list. Note that the defaults should be
# considered "Load"s from the upper scope, and the argument names are
# "Store"s in the function scope.
Expand All @@ -693,8 +686,7 @@ def visit_arguments(self, node):
# Store arg names.
self.visit(node.args)
self.visit(node.kwonlyargs)
if sys.version_info >= (3, 8):
self.visit(node.posonlyargs)
self.visit(node.posonlyargs)
# may be None.
if node.vararg:
self.visit(node.vararg)
Expand Down Expand Up @@ -807,16 +799,12 @@ def visit_Name(self, node):
self._visit_fullname(node.id, node.ctx)

def visit_arg(self, node):
if sys.version_info >= (3, 8):
assert node._fields == ('arg', 'annotation', 'type_comment'), node._fields
else:
assert node._fields == ('arg', 'annotation'), node._fields
assert node._fields == ('arg', 'annotation', 'type_comment'), node._fields
if node.annotation:
self.visit(node.annotation)
# Treat it like a Name node would from Python 2
self._visit_fullname(node.arg, ast.Param())
if sys.version_info >= (3, 8):
self._visit_typecomment(node.type_comment)
self._visit_typecomment(node.type_comment)

def visit_Attribute(self, node):
name_revparts = []
Expand Down Expand Up @@ -1512,8 +1500,7 @@ def find_missing_imports(arg, namespaces):
else:
return []
# Parse the string into an AST.
kw = {} if sys.version_info < (3, 8) else {'type_comments': True}
node = ast.parse(arg, **kw) # may raise SyntaxError
node = ast.parse(arg, type_comments=True) # may raise SyntaxError
# Get missing imports from AST.
return _find_missing_imports_in_ast(node, namespaces)
elif isinstance(arg, PythonBlock):
Expand Down
117 changes: 25 additions & 92 deletions lib/python/pyflyby/_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,7 @@

from ast import Bytes

if sys.version_info >= (3, 8):
from ast import TypeIgnore, AsyncFunctionDef
else:

# TypeIgnore, AsyncFunctionDef does not exist on Python 3.7 and before. thus
# we define a dummy TypeIgnore, AsyncFunctionDef just to simplify remaining
# code.

class TypeIgnore:
pass

class AsyncFunctionDef:
pass
from ast import TypeIgnore, AsyncFunctionDef


def _is_comment_or_blank(line):
Expand Down Expand Up @@ -104,37 +92,27 @@ def _iter_child_nodes_in_order_internal_1(node):
assert node._fields == ("keys", "values")
yield list(zip(node.keys, node.values))
elif isinstance(node, (ast.FunctionDef, AsyncFunctionDef)):
if sys.version_info >= (3, 8):
assert node._fields == (
"name",
"args",
"body",
"decorator_list",
"returns",
"type_comment",
), node._fields
res = (
node.type_comment,
node.decorator_list,
node.args,
node.returns,
node.body,
)
yield res
else:
assert node._fields == ('name', 'args', 'body', 'decorator_list',
'returns'), node._fields
yield node.decorator_list, node.args, node.returns, node.body
assert node._fields == (
"name",
"args",
"body",
"decorator_list",
"returns",
"type_comment",
), node._fields
res = (
node.type_comment,
node.decorator_list,
node.args,
node.returns,
node.body,
)
yield res
# node.name is a string, not an AST node
elif isinstance(node, ast.arguments):
if sys.version_info >= (3, 8):
assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs',
'kw_defaults', 'kwarg', 'defaults'), node._fields
args = node.posonlyargs + node.args
else:
assert node._fields == ('args', 'vararg', 'kwonlyargs',
'kw_defaults', 'kwarg', 'defaults'), node._fields
args = node.args
assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs',
'kw_defaults', 'kwarg', 'defaults'), node._fields
args = node.posonlyargs + node.args
defaults = node.defaults or ()
num_no_default = len(args) - len(defaults)
yield args[:num_no_default]
Expand Down Expand Up @@ -187,22 +165,10 @@ def _flags_to_try(source, flags, auto_flags, mode):
If ``auto_flags`` is True, then yield ``flags`` and ``flags ^ print_function``.
"""
flags = CompilerFlags(flags)
if sys.version_info >= (3, 8):
if re.search(r"# *type:", source):
flags = flags | CompilerFlags('type_comments')
yield flags
return
if not auto_flags:
yield flags
return
if mode == "eval":
if re.search(r"\bprint\b", source):
flags = flags | CompilerFlags("print_function")
yield flags
return
if re.search(r"# *type:", source):
flags = flags | CompilerFlags('type_comments')
yield flags
if re.search(r"\bprint\b", source):
yield flags ^ CompilerFlags("print_function")
return


def _parse_ast_nodes(text, flags, auto_flags, mode):
Expand Down Expand Up @@ -348,14 +314,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
"""
assert isinstance(ast_node, (ast.AST, str, TypeIgnore)), ast_node

# joined strings and children do not carry a column offset on pre-3.8
# this prevent reformatting.
# set the column offset to the parent value before 3.8
if (3, 7) < sys.version_info < (3, 8):
instances = (getattr(ast, "JoinedStr", None), ast.FormattedValue)
if ((isinstance(ast_node, instances) or isinstance(parent_ast_node, instances)) and ast_node.col_offset == -1) or isinstance(ast_node, ast.keyword):
ast_node.col_offset = parent_ast_node.col_offset

# First, traverse child nodes. If the first child node (recursively) is a
# multiline string, then we need to transfer its information to this node.
# Walk all nodes/fields of the AST. We implement this as a custom
Expand Down Expand Up @@ -400,9 +358,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
if ast_node.col_offset >= 0:
# In Python 3.8+, FunctionDef.lineno is the line with the def. To
# account for decorators, we need the lineno of the first decorator
if (sys.version_info >= (3, 8)
and isinstance(ast_node, (ast.FunctionDef, ast.ClassDef, AsyncFunctionDef))
and ast_node.decorator_list):
if (isinstance(ast_node, (ast.FunctionDef, ast.ClassDef, AsyncFunctionDef))
and ast_node.decorator_list):
delta = (ast_node.decorator_list[0].lineno-1,
# The col_offset doesn't include the @
ast_node.decorator_list[0].col_offset - 1)
Expand Down Expand Up @@ -486,9 +443,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
for _m in re.finditer("[bBrRuU]*[\"\']", start_line)])
target_str = ast_node.s

if isinstance(target_str, bytes) and sys.version_info[:2] == (3, 7):
target_str = target_str.decode()

# Loop over possible end_linenos. The first one we've identified is the
# by far most likely one, but in theory it could be anywhere later in the
# file. This could be because of a dastardly concatenated string like
Expand Down Expand Up @@ -553,18 +507,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
candidate_str = _test_parse_string_literal(subtext, flags)
if candidate_str is None:
continue
if isinstance(candidate_str, bytes) and sys.version_info[:2] == (3, 7):
candidate_str = candidate_str.decode()

maybe_fstring = False
try:
if (3, 7) <= sys.version_info <= (3, 8):
potential_start = text.lines[startpos.lineno - 1]
maybe_fstring = ("f'" in potential_start) or (
'f"' in potential_start
)
except IndexError:
pass

if target_str == candidate_str and target_str:
# Success!
Expand All @@ -589,17 +533,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
for (sq, sp) in startpos_candidates
if sp in matched_prefix
]
if (3, 7) <= sys.version_info <= (3, 8):
if len(f_string_candidate_prefixes) == 1:
# we did not find the string but there is one fstring candidate starting it

ast_node.startpos, ast_node.endpos = f_string_candidate_prefixes[0]
return True
elif isinstance(parent_ast_node, ast.JoinedStr):
self_pos = parent_ast_node.values.index(ast_node)
ast_node.startpos = parent_ast_node.values[self_pos - 1].startpos
ast_node.endpos = parent_ast_node.values[self_pos - 1].endpos
return True
raise ValueError("Couldn't find exact position of %s" % (ast.dump(ast_node)))


Expand Down
27 changes: 2 additions & 25 deletions tests/test_autoimp.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,9 +930,7 @@ def f():
expected = ['use', 'y']
assert expected == result

@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only syntax.")

def test_find_missing_imports_positional_only_args_1():
code = dedent("""
def func(x, /, y):
Expand Down Expand Up @@ -1225,11 +1223,6 @@ def test_find_missing_imports_tuple_ellipsis_type_1():
assert expected == result


# Only Python 3.8 includes type comments in the ast, so we only support this
# there (see issue #31).
@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_1():
code = dedent("""
from typing import Sequence
Expand All @@ -1242,11 +1235,6 @@ def foo(strings # type: Sequence[str]
assert missing == []


# Only Python 3.8 includes type comments in the ast, so we only support this
# there (see issue #31, 171, 174).
@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_2():
code = dedent("""
from typing import Sequence
Expand All @@ -1259,9 +1247,6 @@ def foo(strings):
assert missing == []


@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_3():
code = dedent("""
def foo(strings):
Expand All @@ -1273,9 +1258,6 @@ def foo(strings):
assert missing == [(1, DottedIdentifier('Sequence'))]


@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_4():
code = dedent("""
from typing import Sequence, Tuple
Expand All @@ -1287,12 +1269,7 @@ def foo(strings):
assert unused == [(2, Import('from typing import Tuple'))]
assert missing == []

# Python 3.8 uses the correct line number for multiline strings (the first
# line), making _annotate_ast_startpos irrelevant. Otherwise, the logic for
# getting this right is too hard. See issue #12.
@pytest.mark.skipif(
sys.version_info < (3, 8),
reason="Python 3.8+-only support.")

def test_scan_for_import_issues_multiline_string_1():
code = dedent('''
x = (
Expand Down
4 changes: 1 addition & 3 deletions tests/test_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1039,9 +1039,7 @@ def ipython(template, **kwargs):
the template. Assert that the result matches.
"""
__tracebackhide__ = True
if sys.version_info[:2] >= (3, 8):
# more recent IPython have a different formatting.
template = template.replace("... in ...", "... line ...")
template = template.replace("... in ...", "... line ...")
template = dedent(template).strip()
input, expected = parse_template(template, clear_tab_completions=_IPYTHON_VERSION>=(7,))
args = kwargs.pop("args", ())
Expand Down
Loading

0 comments on commit 288b28e

Please sign in to comment.