/
confpage.py
259 lines (226 loc) · 9.99 KB
/
confpage.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
# -*- 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 _
from spyder.api.preferences import PluginConfigPage
from spyder.py3compat import 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
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)
# add custom_interpreter to executable selection
executable = self.get_option('executable')
# check if the executable is valid - use Spyder's if not
if self.get_option('default') or not osp.isfile(executable):
executable = get_python_executable()
elif 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 <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 file 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 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)