# -*- coding: utf-8 -*- # # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Main interpreter entry in Preferences.""" # Standard library imports import os import os.path as osp import sys # Third party imports from qtpy.QtWidgets import (QButtonGroup, QGroupBox, QInputDialog, QLabel, QLineEdit, QMessageBox, QPushButton, QVBoxLayout) # Local imports from spyder.api.translations import get_translation from spyder.api.preferences import PluginConfigPage from spyder.py3compat import PY2, to_text_string from spyder.utils import programs from spyder.utils.conda import get_list_conda_envs_cache from spyder.utils.misc import get_python_executable from spyder.utils.pyenv import get_list_pyenv_envs_cache # Localization _ = get_translation('spyder') class MainInterpreterConfigPage(PluginConfigPage): def __init__(self, plugin, parent): super().__init__(plugin, parent) self.apply_callback = self.perform_adjustments self.cus_exec_radio = None self.pyexec_edit = None self.cus_exec_combo = None conda_env = get_list_conda_envs_cache() pyenv_env = get_list_pyenv_envs_cache() envs = {**conda_env, **pyenv_env} valid_custom_list = self.get_option('custom_interpreters_list') for env in envs.keys(): path, _ = envs[env] if path not in valid_custom_list: valid_custom_list.append(path) self.set_option('custom_interpreters_list', valid_custom_list) # Python executable selection (initializing default values as well) executable = self.get_option('executable', get_python_executable()) if self.get_option('default'): executable = get_python_executable() if not osp.isfile(executable): # This is absolutely necessary, in case the Python interpreter # executable has been moved since last Spyder execution (following # a Python distribution upgrade for example) self.set_option('executable', get_python_executable()) elif executable.endswith('pythonw.exe'): # That should not be necessary because this case is already taken # care of by the `get_python_executable` function but, this was # implemented too late, so we have to fix it here too, in case # the Python executable has already been set with pythonw.exe: self.set_option('executable', executable.replace("pythonw.exe", "python.exe")) if not self.get_option('default'): if not self.get_option('custom_interpreter'): self.set_option('custom_interpreter', ' ') plugin._add_to_custom_interpreters(executable) self.validate_custom_interpreters_list() def initialize(self): super().initialize() def setup_page(self): newcb = self.create_checkbox # Python executable Group pyexec_group = QGroupBox(_("Python interpreter")) pyexec_bg = QButtonGroup(pyexec_group) pyexec_label = QLabel(_("Select the Python interpreter for all Spyder " "consoles")) self.def_exec_radio = self.create_radiobutton( _("Default (i.e. the same as Spyder's)"), 'default', button_group=pyexec_bg, ) self.cus_exec_radio = self.create_radiobutton( _("Use the following Python interpreter:"), 'custom', button_group=pyexec_bg, ) if os.name == 'nt': filters = _("Executables")+" (*.exe)" else: filters = None pyexec_layout = QVBoxLayout() pyexec_layout.addWidget(pyexec_label) pyexec_layout.addWidget(self.def_exec_radio) pyexec_layout.addWidget(self.cus_exec_radio) self.validate_custom_interpreters_list() self.cus_exec_combo = self.create_file_combobox( _('Recent custom interpreters'), self.get_option('custom_interpreters_list'), 'custom_interpreter', filters=filters, default_line_edit=True, adjust_to_contents=True, validate_callback=programs.is_python_interpreter, ) self.def_exec_radio.toggled.connect(self.cus_exec_combo.setDisabled) self.cus_exec_radio.toggled.connect(self.cus_exec_combo.setEnabled) pyexec_layout.addWidget(self.cus_exec_combo) pyexec_group.setLayout(pyexec_layout) self.pyexec_edit = self.cus_exec_combo.combobox.lineEdit() # UMR Group umr_group = QGroupBox(_("User Module Reloader (UMR)")) umr_label = QLabel(_("UMR forces Python to reload modules which were " "imported when executing a file in a Python or " "IPython console with the runfile " "function.")) umr_label.setWordWrap(True) umr_enabled_box = newcb( _("Enable UMR"), 'umr/enabled', msg_if_enabled=True, msg_warning=_( "This option will enable the User Module Reloader (UMR) " "in Python/IPython consoles. UMR forces Python to " "reload deeply modules during import when running a " "Python script using the Spyder's builtin function " "runfile." "

1. UMR may require to restart the " "console in which it will be called " "(otherwise only newly imported modules will be " "reloaded when executing files)." "

2. If errors occur when re-running a " "PyQt-based program, please check that the Qt objects " "are properly destroyed (e.g. you may have to use the " "attribute Qt.WA_DeleteOnClose on your main " "window, using the setAttribute method)" ), ) umr_verbose_box = newcb( _("Show reloaded modules list"), 'umr/verbose', msg_info=_("Please note that these changes will " "be applied only to new consoles"), ) umr_namelist_btn = QPushButton( _("Set UMR excluded (not reloaded) modules")) umr_namelist_btn.clicked.connect(self.set_umr_namelist) umr_layout = QVBoxLayout() umr_layout.addWidget(umr_label) umr_layout.addWidget(umr_enabled_box) umr_layout.addWidget(umr_verbose_box) umr_layout.addWidget(umr_namelist_btn) umr_group.setLayout(umr_layout) vlayout = QVBoxLayout() vlayout.addWidget(pyexec_group) vlayout.addWidget(umr_group) vlayout.addStretch(1) self.setLayout(vlayout) def warn_python_compatibility(self, pyexec): if not osp.isfile(pyexec): return spyder_version = sys.version_info[0] try: args = ["-c", "import sys; print(sys.version_info[0])"] proc = programs.run_program(pyexec, args, env={}) console_version = int(proc.communicate()[0]) except IOError: console_version = spyder_version except ValueError: return False if spyder_version != console_version: QMessageBox.warning( self, _('Warning'), _("You selected a Python %d interpreter for the console " "but Spyder is running on Python %d!.

" "Although this is possible, we recommend you to install and " "run Spyder directly with your selected interpreter, to avoid " "seeing false warnings and errors due to the incompatible " "syntax between these two Python versions." ) % (console_version, spyder_version), QMessageBox.Ok, ) return True def set_umr_namelist(self): """Set UMR excluded modules name list""" arguments, valid = QInputDialog.getText( self, _('UMR'), _("Set the list of excluded modules as this: " "numpy, scipy"), QLineEdit.Normal, ", ".join(self.get_option('umr/namelist')), ) if valid: arguments = to_text_string(arguments) if arguments: namelist = arguments.replace(' ', '').split(',') fixed_namelist = [] non_ascii_namelist = [] for module_name in namelist: if PY2: if all(ord(c) < 128 for c in module_name): if programs.is_module_installed(module_name): fixed_namelist.append(module_name) else: QMessageBox.warning( self, _('Warning'), _("You are working with Python 2, this means " "that you can not import a module that " "contains non-ascii characters."), QMessageBox.Ok, ) non_ascii_namelist.append(module_name) elif programs.is_module_installed(module_name): fixed_namelist.append(module_name) invalid = ", ".join(set(namelist)-set(fixed_namelist)- set(non_ascii_namelist)) if invalid: QMessageBox.warning( self, _('UMR'), _("The following modules are not " "installed on your machine:\n%s") % invalid, QMessageBox.Ok, ) QMessageBox.information( self, _('UMR'), _("Please note that these changes will " "be applied only to new IPython consoles"), QMessageBox.Ok, ) else: fixed_namelist = [] self.set_option('umr/namelist', fixed_namelist) def validate_custom_interpreters_list(self): """Check that the used custom interpreters are still valid.""" custom_list = self.get_option('custom_interpreters_list') valid_custom_list = [] for value in custom_list: if osp.isfile(value): valid_custom_list.append(value) self.set_option('custom_interpreters_list', valid_custom_list) def perform_adjustments(self): """Perform some adjustments to the page after applying preferences.""" if not self.def_exec_radio.isChecked(): # Get current executable executable = self.pyexec_edit.text() executable = osp.normpath(executable) if executable.endswith('pythonw.exe'): executable = executable.replace("pythonw.exe", "python.exe") # Update combobox items. custom_list = self.cus_exec_combo.combobox.choices if executable not in custom_list: custom_list = custom_list + [executable] self.cus_exec_combo.combobox.clear() self.cus_exec_combo.combobox.addItems(custom_list) self.pyexec_edit.setText(executable) # Show warning compatibility message. self.warn_python_compatibility(executable)