diff --git a/mypy/main.py b/mypy/main.py index a7154aa3f961..7b72825f4b7c 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -227,17 +227,6 @@ def python_executable_prefix(v: str) -> List[str]: return ['python{}'.format(v)] -def _python_version_from_executable(python_executable: str) -> Tuple[int, int]: - try: - check = subprocess.check_output([python_executable, '-c', - 'import sys; print(repr(sys.version_info[:2]))'], - stderr=subprocess.STDOUT).decode() - return ast.literal_eval(check) - except (subprocess.CalledProcessError, FileNotFoundError): - raise PythonExecutableInferenceError( - 'invalid Python executable {}'.format(python_executable)) - - def _python_executable_from_version(python_version: Tuple[int, int]) -> str: if sys.version_info[:2] == python_version: return sys.executable @@ -253,37 +242,25 @@ def _python_executable_from_version(python_version: Tuple[int, int]) -> str: ' perhaps try --python-executable, or --no-site-packages?'.format(python_version)) -def infer_python_version_and_executable(options: Options, - special_opts: argparse.Namespace) -> None: - """Infer the Python version or executable from each other. Check they are consistent. +def infer_python_executable(options: Options, + special_opts: argparse.Namespace) -> None: + """Infer the Python executable from the given version. - This function mutates options based on special_opts to infer the correct Python version and - executable to use. + This function mutates options based on special_opts to infer the correct Python executable + to use. """ - # Infer Python version and/or executable if one is not given - # TODO: (ethanhs) Look at folding these checks and the site packages subprocess calls into # one subprocess call for speed. - if special_opts.python_executable is not None and special_opts.python_version is not None: - py_exe_ver = _python_version_from_executable(special_opts.python_executable) - if py_exe_ver != special_opts.python_version: - raise PythonExecutableInferenceError( - 'Python version {} did not match executable {}, got version {}.'.format( - special_opts.python_version, special_opts.python_executable, py_exe_ver - )) - else: - options.python_version = special_opts.python_version - options.python_executable = special_opts.python_executable - elif special_opts.python_executable is None and special_opts.python_version is not None: - options.python_version = special_opts.python_version - py_exe = None + + # Use the command line specified executable, or fall back to one set in the + # config file. If an executable is not specified, infer it from the version + # (unless no_executable is set) + python_executable = special_opts.python_executable or options.python_executable + + if python_executable is None: if not special_opts.no_executable: - py_exe = _python_executable_from_version(special_opts.python_version) - options.python_executable = py_exe - elif special_opts.python_version is None and special_opts.python_executable is not None: - options.python_version = _python_version_from_executable( - special_opts.python_executable) - options.python_executable = special_opts.python_executable + python_executable = _python_executable_from_version(options.python_version) + options.python_executable = python_executable HEADER = """%(prog)s [-h] [-v] [-V] [more options; see below] @@ -744,8 +721,11 @@ def add_invertible_flag(flag: str, print("Warning: --quick-and-dirty is deprecated. It will disappear in the next release.", file=sys.stderr) + # The python_version is either the default, which can be overridden via a config file, + # or stored in special_opts and is passed via the command line. + options.python_version = special_opts.python_version or options.python_version try: - infer_python_version_and_executable(options, special_opts) + infer_python_executable(options, special_opts) except PythonExecutableInferenceError as e: parser.error(str(e)) diff --git a/mypy/test/testargs.py b/mypy/test/testargs.py index bccbafaf4c8b..7c1e9ef45c56 100644 --- a/mypy/test/testargs.py +++ b/mypy/test/testargs.py @@ -12,7 +12,7 @@ from mypy.test.helpers import Suite, assert_equal from mypy.options import Options from mypy.main import (process_options, PythonExecutableInferenceError, - infer_python_version_and_executable) + infer_python_executable) class ArgSuite(Suite): @@ -47,22 +47,30 @@ def test_executable_inference(self) -> None: assert options.python_version == sys.version_info[:2] assert options.python_executable == sys.executable - # test that we error if the version mismatch - # argparse sys.exits on a parser.error, we need to check the raw inference function - options = Options() - - special_opts = argparse.Namespace() - special_opts.python_executable = sys.executable - special_opts.python_version = (2, 10) # obviously wrong - special_opts.no_executable = None - with pytest.raises(PythonExecutableInferenceError) as e: - infer_python_version_and_executable(options, special_opts) - assert str(e.value) == 'Python version (2, 10) did not match executable {}, got' \ - ' version {}.'.format(sys.executable, sys.version_info[:2]) - # test that --no-site-packages will disable executable inference matching_version = base + ['--python-version={}'.format(sys_ver_str), '--no-site-packages'] _, options = process_options(matching_version) assert options.python_version == sys.version_info[:2] assert options.python_executable is None + + # Test setting python_version/executable from config file + special_opts = argparse.Namespace() + special_opts.python_executable = None + special_opts.python_version = None + special_opts.no_executable = None + + # first test inferring executable from version + options = Options() + options.python_executable = None + options.python_version = sys.version_info[:2] + infer_python_executable(options, special_opts) + assert options.python_version == sys.version_info[:2] + assert options.python_executable == sys.executable + + # then test inferring version from executable + options = Options() + options.python_executable = sys.executable + infer_python_executable(options, special_opts) + assert options.python_version == sys.version_info[:2] + assert options.python_executable == sys.executable