Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting python_executable from config file... #5777

Merged
merged 8 commits into from Dec 17, 2018
56 changes: 18 additions & 38 deletions mypy/main.py
Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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))

Expand Down
36 changes: 22 additions & 14 deletions mypy/test/testargs.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this testing anything any more? You don't infer the version from the executable any more in the code.

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