/
maininterpreter.py
275 lines (247 loc) · 12.9 KB
/
maininterpreter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# -*- 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
from __future__ import print_function
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.config.base import _
from spyder.preferences.configdialog import GeneralConfigPage
from spyder.py3compat import PY2, is_text_string, to_text_string
from spyder.utils import icon_manager as ima
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
class MainInterpreterConfigPage(GeneralConfigPage):
CONF_SECTION = "main_interpreter"
NAME = _("Python interpreter")
ICON = ima.icon('python')
def __init__(self, parent, main):
GeneralConfigPage.__init__(self, parent, main)
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', ' ')
self.set_custom_interpreters_list(executable)
self.validate_custom_interpreters_list()
def initialize(self):
GeneralConfigPage.initialize(self)
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 <i>runfile</i> "
"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 "
"<b>runfile</b>."
"<br><br><b>1.</b> UMR may require to restart the "
"console in which it will be called "
"(otherwise only newly imported modules will be "
"reloaded when executing files)."
"<br><br><b>2.</b> 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 <b>Qt.WA_DeleteOnClose</b> on your main "
"window, using the <b>setAttribute</b> 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 <b>Python %d</b> interpreter for the console "
"but Spyder is running on <b>Python %d</b>!.<br><br>"
"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: <i>numpy, scipy</i>"),
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 Python/IPython "
"consoles"), QMessageBox.Ok)
else:
fixed_namelist = []
self.set_option('umr/namelist', fixed_namelist)
def set_custom_interpreters_list(self, value):
"""Update the list of interpreters used and the current one."""
custom_list = self.get_option('custom_interpreters_list')
if value not in custom_list:
custom_list.append(value)
self.set_option('custom_interpreters_list', custom_list)
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 apply_settings(self, options):
if not self.def_exec_radio.isChecked():
executable = self.pyexec_edit.text()
executable = osp.normpath(executable)
if executable.endswith('pythonw.exe'):
executable = executable.replace("pythonw.exe", "python.exe")
# Set new interpreter
self.set_custom_interpreters_list(executable)
self.set_option('executable', executable)
self.set_option('custom_interpreter', executable)
# Update combobox items.
custom_list = self.get_option('custom_interpreters_list')
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)
if not self.pyexec_edit.text():
self.set_option('custom_interpreter', '')
else:
# This prevents showing an incorrect file name in the custom
# line edit.
if not programs.is_python_interpreter(self.pyexec_edit.text()):
self.pyexec_edit.clear()
self.set_option('custom_interpreter', '')
if ('default' in options or 'custom' in options
or 'custom_interpreter' in options):
self.main.sig_main_interpreter_changed.emit()
self.main.apply_statusbar_settings()