diff --git a/.travis.yml b/.travis.yml index 1a95050cd5be..227213fdebc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index eac41ddbef50..758a458fee92 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 1c73f7ae4347..a4da8ec13546 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -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``. diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index b5157101568c..fd206491f88a 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -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 `_, install mypy using pip: diff --git a/mypy/build.py b/mypy/build.py index 9479351daac3..c6c44fd6ead8 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -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() @@ -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], diff --git a/mypy/dmypy.py b/mypy/dmypy.py index e4f77673f801..6896d84b5a91 100644 --- a/mypy/dmypy.py +++ b/mypy/dmypy.py @@ -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__ @@ -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() diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index 79e5769fee2e..3b464a80afab 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -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 @@ -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__ diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 7d890570519b..9cad8eedf174 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -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) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 016a137c50a3..365edeb00048 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -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) diff --git a/mypy/main.py b/mypy/main.py index ab23f90c7fde..b7668b98d45f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -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) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 6bf5c2e683f0..bfe4dea09964 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -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): diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index f23e3d739735..7a3ea0a831da 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -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 diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index ed0e56e55181..4b988503df79 100644 --- a/mypy/test/testpep561.py +++ b/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 @@ -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 @@ -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')) @@ -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) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index bd507e630e8b..89a3afaca110 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -13,6 +13,8 @@ import os import os.path import re +import subprocess +from subprocess import PIPE import sys from tempfile import TemporaryDirectory @@ -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 @@ -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): diff --git a/mypy/util.py b/mypy/util.py index df054814df4e..4daf7de5f0cc 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -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)) diff --git a/runtests.py b/runtests.py index 9a2ac05a9115..36c2dfa2a014 100755 --- a/runtests.py +++ b/runtests.py @@ -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': diff --git a/setup.py b/setup.py index 07c085133e23..e3dc63889861 100644 --- a/setup.py +++ b/setup.py @@ -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 @@ -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', @@ -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, diff --git a/tox.ini b/tox.ini index fc2604cc7f77..47197c1e87ae 100644 --- a/tox.ini +++ b/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,