Repository: spyder-ide/spyder

Issue:
Verify that the interpreter selected in Main Interpreter is really a Python interpreter
It seems people sometimes selects a wrong Python executable in

    Preferences > Main Interpreter

and that leads to not being able to start Python or IPython consoles (see for example issue #3750).

Because of that we need to verify that they are correctly selecting a Python interpreter and not other executables (e.g. the IPython one).

----

@mariacamilaremolinagutierrez, please work on this one.


Discussion:

Resolving patch:
diff --git a/spyder/plugins/maininterpreter.py b/spyder/plugins/maininterpreter.py
--- a/spyder/plugins/maininterpreter.py
+++ b/spyder/plugins/maininterpreter.py
@@ -141,7 +141,15 @@ def python_executable_changed(self, pyexec):
             return
         if not is_text_string(pyexec):
             pyexec = to_text_string(pyexec.toUtf8(), 'utf-8')
-        self.warn_python_compatibility(pyexec)
+        if programs.is_python_interpreter(pyexec):
+            self.warn_python_compatibility(pyexec)
+        else:
+            QMessageBox.warning(self, _('Warning'),
+                    _("You selected an invalid Python interpreter for the "
+                      "console so the previous interpreter will stay. Please "
+                      "make sure to select a valid one."), QMessageBox.Ok)
+            self.pyexec_edit.setText(get_python_executable())
+            return
 
     def python_executable_switched(self, custom):
         """Python executable default/custom radio button has been toggled"""
diff --git a/spyder/utils/programs.py b/spyder/utils/programs.py
--- a/spyder/utils/programs.py
+++ b/spyder/utils/programs.py
@@ -20,7 +20,7 @@
 
 # Local imports
 from spyder.utils import encoding
-from spyder.py3compat import PY2, is_text_string
+from spyder.py3compat import PY2, is_text_string, to_text_string
 
 
 class ProgramError(Exception):
@@ -462,6 +462,32 @@ def is_module_installed(module_name, version=None, installed_version=None,
             
             return check_version(actver, version, symb)
 
+def is_python_interpreter_valid_name(filename):
+    """Check that the python interpreter file has a valid name."""
+    pattern = r'.*python(\d\.?\d*)?(w)?(.exe)?$'
+    if re.match(pattern, filename, flags=re.I) is None:
+        return False
+    else:
+        return True
+
+def is_python_interpreter(filename):
+    """Evaluate wether a file is a python interpreter or not."""
+    real_filename = os.path.realpath(filename)  # To follow symlink if existent
+    if (not osp.isfile(real_filename) or encoding.is_text_file(real_filename)
+        or not is_python_interpreter_valid_name(filename)):
+        return False
+    try:
+        proc = run_program(filename, ["-h"])
+        output = to_text_string(proc.communicate()[0])
+        valid = ("Options and arguments (and corresponding environment "
+                 "variables)")
+        if 'usage:' in output and valid in output:
+            return True
+        else:
+            return False
+    except:
+        return False
+
 
 def test_programs():
     assert find_program('git')


Test patch:
diff --git a/spyder/utils/tests/test_programs.py b/spyder/utils/tests/test_programs.py
--- a/spyder/utils/tests/test_programs.py
+++ b/spyder/utils/tests/test_programs.py
@@ -6,10 +6,22 @@
 """Tests for programs.py"""
 
 import os
-
 import pytest
 
-from spyder.utils.programs import run_python_script_in_terminal
+from spyder.utils.programs import (run_python_script_in_terminal,
+                                   is_python_interpreter,
+                                   is_python_interpreter_valid_name)
+
+
+if os.name == 'nt':
+    python_dir = os.environ['PYTHON']
+    VALID_INTERPRETER = os.path.join(python_dir, 'python.exe')
+    INVALID_INTERPRETER = os.path.join(python_dir, 'Scripts', 'ipython.exe')
+else:
+    home_dir = os.environ['HOME']
+    VALID_INTERPRETER = os.path.join(home_dir, 'miniconda', 'bin', 'python')
+    INVALID_INTERPRETER = os.path.join(home_dir, 'miniconda', 'bin', 'ipython')
+
 
 @pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
 def test_run_python_script_in_terminal(tmpdir, qtbot):
@@ -24,6 +36,7 @@ def test_run_python_script_in_terminal(tmpdir, qtbot):
     res = outfilepath.read()
     assert res == 'done'
 
+
 @pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
 def test_run_python_script_in_terminal_with_wdir_empty(tmpdir, qtbot):
     scriptpath = tmpdir.join('write-done.py')
@@ -36,7 +49,24 @@ def test_run_python_script_in_terminal_with_wdir_empty(tmpdir, qtbot):
     res = outfilepath.read()
     assert res == 'done'
 
-    
+
+@pytest.mark.skipif(os.environ.get('CI', None) == True,
+                    reason='It only runs in CI services.')
+def test_is_valid_interpreter():
+    assert is_python_interpreter(VALID_INTERPRETER)
+
+
+@pytest.mark.skipif(os.environ.get('CI', None) == True,
+                    reason='It only runs in CI services.')
+def test_is_invalid_interpreter():
+    assert not is_python_interpreter(INVALID_INTERPRETER)
+
+
+def test_is_valid_interpreter_name():
+    names = ['python', 'pythonw', 'python2.7', 'python3.5', 'python.exe', 'pythonw.exe']
+    assert all([is_python_interpreter_valid_name(n) for n in names])
+
+
 if __name__ == '__main__':
     pytest.main()