Skip to content

Commit

Permalink
Drop support for running with 3.4 (#6592)
Browse files Browse the repository at this point in the history
Fixes #6564
  • Loading branch information
ethanhs committed Mar 27, 2019
1 parent 9b55e31 commit affb032
Show file tree
Hide file tree
Showing 18 changed files with 62 additions and 134 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Expand Up @@ -14,9 +14,6 @@ env:

jobs:
include:
- name: "run test suite with python 3.4"
python: 3.4
dist: trusty
# Specifically request 3.5.1 because we need to be compatible with that.
- name: "run test suite with python 3.5.1"
python: 3.5.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -64,7 +64,7 @@ See 'Development status' below.
Requirements
------------

You need Python 3.4 or later to run mypy. You can have multiple Python
You need Python 3.5 or later to run mypy. You can have multiple Python
versions (2.x and 3.x) installed on the same system without problems.

In Ubuntu, Mint and Debian you can install Python 3 like this:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/common_issues.rst
Expand Up @@ -14,7 +14,7 @@ Can't install mypy using pip

If installation fails, you've probably hit one of these issues:

* Mypy needs Python 3.4 or later to run.
* Mypy needs Python 3.5 or later to run.
* You may have to run pip like this:
``python3 -m pip install mypy``.

Expand Down
2 changes: 1 addition & 1 deletion docs/source/getting_started.rst
Expand Up @@ -12,7 +12,7 @@ may not make much sense otherwise.
Installing and running mypy
***************************

Mypy requires Python 3.4 or later to run. Once you've
Mypy requires Python 3.5 or later to run. Once you've
`installed Python 3 <https://www.python.org/downloads/>`_,
install mypy using pip:

Expand Down
8 changes: 2 additions & 6 deletions mypy/build.py
Expand Up @@ -2961,9 +2961,7 @@ def dfs(v: str) -> Iterator[Set[str]]:

for w in edges[v]:
if w not in index:
# For Python >= 3.3, replace with "yield from dfs(w)"
for scc in dfs(w):
yield scc
yield from dfs(w)
elif w not in identified:
while index[w] < boundaries[-1]:
boundaries.pop()
Expand All @@ -2977,9 +2975,7 @@ def dfs(v: str) -> Iterator[Set[str]]:

for v in vertices:
if v not in index:
# For Python >= 3.3, replace with "yield from dfs(v)"
for scc in dfs(v):
yield scc
yield from dfs(v)


def topsort(data: Dict[AbstractSet[str],
Expand Down
2 changes: 2 additions & 0 deletions mypy/dmypy.py
Expand Up @@ -18,6 +18,7 @@
from mypy.dmypy_util import DEFAULT_STATUS_FILE, receive
from mypy.ipc import IPCClient, IPCException
from mypy.dmypy_os import alive, kill
from mypy.util import check_python_version

from mypy.version import __version__

Expand Down Expand Up @@ -131,6 +132,7 @@ class BadStatus(Exception):

def main(argv: List[str]) -> None:
"""The code is top-down."""
check_python_version('dmypy')
args = parser.parse_args(argv)
if not args.action:
parser.print_usage()
Expand Down
2 changes: 1 addition & 1 deletion mypy/dmypy_server.py
Expand Up @@ -14,6 +14,7 @@
import sys
import time
import traceback
from contextlib import redirect_stderr, redirect_stdout

from typing import AbstractSet, Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple

Expand All @@ -30,7 +31,6 @@
from mypy.options import Options
from mypy.suggestions import SuggestionFailure, SuggestionEngine
from mypy.typestate import reset_global_state
from mypy.util import redirect_stderr, redirect_stdout
from mypy.version import __version__


Expand Down
24 changes: 10 additions & 14 deletions mypy/fastparse.py
Expand Up @@ -98,21 +98,17 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
NamedExpr = Any
Constant = Any
except ImportError:
if sys.version_info.minor > 2:
try:
from typed_ast import ast35 # type: ignore
except ImportError:
print('The typed_ast package is not installed.\n'
'You can install it with `python3 -m pip install typed-ast`.',
file=sys.stderr)
else:
print('You need a more recent version of the typed_ast package.\n'
'You can update to the latest version with '
'`python3 -m pip install -U typed-ast`.',
file=sys.stderr)
try:
from typed_ast import ast35 # type: ignore
except ImportError:
print('The typed_ast package is not installed.\n'
'You can install it with `python3 -m pip install typed-ast`.',
file=sys.stderr)
else:
print('Mypy requires the typed_ast package, which is only compatible with\n'
'Python 3.3 and greater.', file=sys.stderr)
print('You need a more recent version of the typed_ast package.\n'
'You can update to the latest version with '
'`python3 -m pip install -U typed-ast`.',
file=sys.stderr)
sys.exit(1)

N = TypeVar('N', bound=Node)
Expand Down
24 changes: 10 additions & 14 deletions mypy/fastparse2.py
Expand Up @@ -60,21 +60,17 @@
# Import ast3 from fastparse, which has special case for Python 3.8
from mypy.fastparse import ast3, ast3_parse
except ImportError:
if sys.version_info.minor > 2:
try:
from typed_ast import ast35 # type: ignore
except ImportError:
print('The typed_ast package is not installed.\n'
'You can install it with `python3 -m pip install typed-ast`.',
file=sys.stderr)
else:
print('You need a more recent version of the typed_ast package.\n'
'You can update to the latest version with '
'`python3 -m pip install -U typed-ast`.',
file=sys.stderr)
try:
from typed_ast import ast35 # type: ignore
except ImportError:
print('The typed_ast package is not installed.\n'
'You can install it with `python3 -m pip install typed-ast`.',
file=sys.stderr)
else:
print('Mypy requires the typed_ast package, which is only compatible with\n'
'Python 3.3 and greater.', file=sys.stderr)
print('You need a more recent version of the typed_ast package.\n'
'You can update to the latest version with '
'`python3 -m pip install -U typed-ast`.',
file=sys.stderr)
sys.exit(1)

N = TypeVar('N', bound=Node)
Expand Down
9 changes: 1 addition & 8 deletions mypy/main.py
Expand Up @@ -53,14 +53,7 @@ def main(script_path: Optional[str], args: Optional[List[str]] = None) -> None:
args: Custom command-line arguments. If not given, sys.argv[1:] will
be used.
"""
# Check for known bad Python versions.
if sys.version_info[:2] < (3, 4):
sys.exit("Running mypy with Python 3.3 or lower is not supported; "
"please upgrade to 3.4 or newer")
if sys.version_info[:3] == (3, 5, 0):
sys.exit("Running mypy with Python 3.5.0 is not supported; "
"please upgrade to 3.5.1 or newer")

util.check_python_version('mypy')
t0 = time.time()
# To log stat() calls: os.stat = stat_proxy
sys.setrecursionlimit(2 ** 14)
Expand Down
1 change: 1 addition & 0 deletions mypy/stubgen.py
Expand Up @@ -1181,6 +1181,7 @@ def parse_options(args: List[str]) -> Options:


def main() -> None:
mypy.util.check_python_version('stubgen')
# Make sure that the current directory is in sys.path so that
# stubgen can be run on packages in the current directory.
if not ('' in sys.path or '.' in sys.path):
Expand Down
18 changes: 0 additions & 18 deletions mypy/test/helpers.py
Expand Up @@ -397,24 +397,6 @@ def split_lines(*streams: bytes) -> List[str]:
]


def run_command(cmdline: List[str], *, env: Optional[Dict[str, str]] = None,
timeout: int = 300, cwd: str = test_temp_dir) -> Tuple[int, List[str]]:
"""A poor man's subprocess.run() for 3.4 compatibility."""
process = subprocess.Popen(
cmdline,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
)
try:
out, err = process.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
out = err = b''
process.kill()
return process.returncode, split_lines(out, err)


def copy_and_fudge_mtime(source_path: str, target_path: str) -> None:
# In some systems, mtime has a resolution of 1 second which can
# cause annoying-to-debug issues when a file has the same size
Expand Down
23 changes: 12 additions & 11 deletions mypy/test/testpep561.py
@@ -1,6 +1,8 @@
from contextlib import contextmanager
from enum import Enum
import os
import subprocess
from subprocess import PIPE
import sys
import tempfile
from typing import Tuple, List, Generator, Optional
Expand All @@ -9,7 +11,6 @@
import mypy.api
from mypy.modulefinder import get_site_packages_dirs
from mypy.test.config import package_path
from mypy.test.helpers import run_command
from mypy.util import try_find_python2_interpreter

# NOTE: options.use_builtins_fixtures should not be set in these
Expand Down Expand Up @@ -135,13 +136,13 @@ def virtualenv(self,
# Sadly, we need virtualenv, as the Python 3 venv module does not support creating a venv
# for Python 2, and Python 2 does not have its own venv.
with tempfile.TemporaryDirectory() as venv_dir:
returncode, lines = run_command([sys.executable,
'-m',
'virtualenv',
'-p{}'.format(python_executable),
venv_dir], cwd=os.getcwd())
if returncode != 0:
err = '\n'.join(lines)
proc = subprocess.run([sys.executable,
'-m',
'virtualenv',
'-p{}'.format(python_executable),
venv_dir], cwd=os.getcwd(), stdout=PIPE, stderr=PIPE)
if proc.returncode != 0:
err = proc.stdout.decode('utf-8') + proc.stderr.decode('utf-8')
self.fail("Failed to create venv. Do you have virtualenv installed?\n" + err)
if sys.platform == 'win32':
yield venv_dir, os.path.abspath(os.path.join(venv_dir, 'Scripts', 'python'))
Expand All @@ -165,9 +166,9 @@ def install_package(self, pkg: str,
install_cmd.append('develop')
else:
install_cmd.append('install')
returncode, lines = run_command(install_cmd, cwd=working_dir)
if returncode != 0:
self.fail('\n'.join(lines))
proc = subprocess.run(install_cmd, cwd=working_dir, stdout=PIPE, stderr=PIPE)
if proc.returncode != 0:
self.fail(proc.stdout.decode('utf-8') + proc.stderr.decode('utf-8'))

def setUp(self) -> None:
self.simple_prog = ExampleProg(SIMPLE_PROGRAM)
Expand Down
8 changes: 5 additions & 3 deletions mypy/test/testpythoneval.py
Expand Up @@ -13,6 +13,8 @@
import os
import os.path
import re
import subprocess
from subprocess import PIPE
import sys
from tempfile import TemporaryDirectory

Expand All @@ -23,7 +25,7 @@
from mypy.defaults import PYTHON3_VERSION
from mypy.test.config import test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import assert_string_arrays_equal, run_command
from mypy.test.helpers import assert_string_arrays_equal, split_lines
from mypy.util import try_find_python2_interpreter
from mypy import api

Expand Down Expand Up @@ -90,8 +92,8 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None
output.append(line.rstrip("\r\n"))
if returncode == 0:
# Execute the program.
returncode, interp_out = run_command([interpreter, program])
output.extend(interp_out)
proc = subprocess.run([interpreter, program], cwd=test_temp_dir, stdout=PIPE, stderr=PIPE)
output.extend(split_lines(proc.stdout, proc.stderr))
# Remove temp file.
os.remove(program_path)
for i, line in enumerate(output):
Expand Down
55 changes: 10 additions & 45 deletions mypy/util.py
Expand Up @@ -259,48 +259,13 @@ def unmangle(name: str) -> str:
return name.rstrip("'")


# The following is a backport of stream redirect utilities from Lib/contextlib.py
# We need this for 3.4 support. They can be removed in March 2019!


class _RedirectStream:

_stream = None # type: ClassVar[str]

def __init__(self, new_target: TextIO) -> None:
self._new_target = new_target
# We use a list of old targets to make this CM re-entrant
self._old_targets = [] # type: List[TextIO]

def __enter__(self) -> TextIO:
self._old_targets.append(getattr(sys, self._stream))
setattr(sys, self._stream, self._new_target)
return self._new_target

def __exit__(self,
exc_ty: 'Optional[Type[BaseException]]' = None,
exc_val: Optional[BaseException] = None,
exc_tb: Optional[TracebackType] = None,
) -> bool:
setattr(sys, self._stream, self._old_targets.pop())
return False


class redirect_stdout(_RedirectStream):
"""Context manager for temporarily redirecting stdout to another file.
# How to send help() to stderr
with redirect_stdout(sys.stderr):
help(dir)
# How to write help() to a file
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
"""

_stream = "stdout"


class redirect_stderr(_RedirectStream):
"""Context manager for temporarily redirecting stderr to another file."""

_stream = "stderr"
def check_python_version(program: str) -> None:
"""Report issues with the Python used to run mypy, dmypy, or stubgen"""
# Check for known bad Python versions.
if sys.version_info[:2] < (3, 5):
sys.exit("Running {name} with Python 3.4 or lower is not supported; "
"please upgrade to 3.5 or newer".format(name=program))
# this can be deleted once we drop support for 3.5
if sys.version_info[:3] == (3, 5, 0):
sys.exit("Running {name} with Python 3.5.0 is not supported; "
"please upgrade to 3.5.1 or newer".format(name=program))
2 changes: 1 addition & 1 deletion runtests.py
Expand Up @@ -6,7 +6,7 @@


# Use the Python provided to execute the script, or fall back to a sane default
if version_info >= (3, 4, 0):
if version_info >= (3, 5, 0):
python_name = executable
else:
if platform == 'win32':
Expand Down
6 changes: 2 additions & 4 deletions setup.py
Expand Up @@ -5,8 +5,8 @@
import os.path
import sys

if sys.version_info < (3, 4, 0):
sys.stderr.write("ERROR: You need Python 3.4 or later to use mypy.\n")
if sys.version_info < (3, 5, 0):
sys.stderr.write("ERROR: You need Python 3.5 or later to use mypy.\n")
exit(1)

# we'll import stuff from the source tree, let's ensure is on the sys path
Expand Down Expand Up @@ -150,7 +150,6 @@ def run(self):
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
Expand Down Expand Up @@ -179,7 +178,6 @@ def run(self):
'mypy_extensions >= 0.4.0, < 0.5.0',
],
extras_require = {
':python_version < "3.5"': 'typing >= 3.5.3',
'dmypy': 'psutil >= 5.4.0, < 5.5.0; sys_platform!="win32"',
},
include_package_data=True,
Expand Down
5 changes: 2 additions & 3 deletions tox.ini
@@ -1,8 +1,7 @@
[tox]
minversion = 3.4.0
minversion = 3.5.1
skip_missing_interpreters = true
envlist = py34,
py35,
envlist = py35,
py36,
py37,
lint,
Expand Down

0 comments on commit affb032

Please sign in to comment.