From 33c9cdac1106152ab97eaeab81acf76e927a0179 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 20 Aug 2024 12:05:39 -0500 Subject: [PATCH 1/5] Main interpreter: Add sig_interpreter_changed to the plugin - Before that signal was at the container level, which is incorrect because other plugins were using it. - Also, change its signature to send the interpreter path. That simplifies the way the interpreter is updated in Completions. - Make the necessary changes in Completions to accommodate for those changes. --- spyder/app/tests/test_mainwindow.py | 6 ++--- spyder/plugins/completion/api.py | 6 ++--- spyder/plugins/completion/plugin.py | 27 +++++++++++-------- .../providers/languageserver/provider.py | 22 +++++---------- spyder/plugins/maininterpreter/container.py | 14 +++++++--- spyder/plugins/maininterpreter/plugin.py | 11 ++++++++ 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index 352559aa170..7b214b4988e 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -3359,7 +3359,7 @@ def test_preferences_change_interpreter(qtbot, main_window): lsp = main_window.completions.get_provider('lsp') config = lsp.generate_python_config() jedi = config['configurations']['pylsp']['plugins']['jedi'] - assert jedi['environment'] is sys.executable + assert jedi['environment'] == sys.executable assert jedi['extra_paths'] == [] # Get conda env to use @@ -3372,9 +3372,9 @@ def test_preferences_change_interpreter(qtbot, main_window): page.cus_exec_radio.radiobutton.setChecked(True) page.cus_exec_combo.combobox.setCurrentText(conda_env) - mi_container = main_window.main_interpreter.get_container() + main_interpreter = main_window.main_interpreter with qtbot.waitSignal( - mi_container.sig_interpreter_changed, timeout=5000, raising=True + main_interpreter.sig_interpreter_changed, timeout=5000, raising=True ): dlg.ok_btn.animateClick() diff --git a/spyder/plugins/completion/api.py b/spyder/plugins/completion/api.py index d87887df085..f9de9a37526 100644 --- a/spyder/plugins/completion/api.py +++ b/spyder/plugins/completion/api.py @@ -1073,9 +1073,9 @@ def python_path_update(self, previous_path, new_path): """ pass - @Slot() - def main_interpreter_changed(self): - """Handle changes on the main Python interpreter of Spyder.""" + @Slot(str) + def interpreter_changed(self, interpreter): + """Handle changes to the Python interpreter used for completions.""" pass def file_opened_closed_or_updated(self, filename: str, language: str): diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 5d040af77dd..0745b3157d1 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -135,9 +135,15 @@ class CompletionPlugin(SpyderPluginV2): New PythonPath settings. """ - sig_interpreter_changed = Signal() + sig_interpreter_changed = Signal(str) """ - This signal is used to report changes on the main Python interpreter. + This signal is used to handle changes in the Python interpreter done by + other plugins. + + Parameters + ---------- + path: str + Path to the new interpreter. """ sig_language_completions_available = Signal(dict, str) @@ -275,10 +281,9 @@ def on_preferences_available(self): @on_plugin_available(plugin=Plugins.MainInterpreter) def on_maininterpreter_available(self): maininterpreter = self.get_plugin(Plugins.MainInterpreter) - mi_container = maininterpreter.get_container() - - mi_container.sig_interpreter_changed.connect( - self.sig_interpreter_changed) + maininterpreter.sig_interpreter_changed.connect( + self.sig_interpreter_changed + ) @on_plugin_available(plugin=Plugins.StatusBar) def on_statusbar_available(self): @@ -313,10 +318,9 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.MainInterpreter) def on_maininterpreter_teardown(self): maininterpreter = self.get_plugin(Plugins.MainInterpreter) - mi_container = maininterpreter.get_container() - - mi_container.sig_interpreter_changed.disconnect( - self.sig_interpreter_changed) + maininterpreter.sig_interpreter_changed.disconnect( + self.sig_interpreter_changed + ) @on_plugin_teardown(plugin=Plugins.StatusBar) def on_statusbar_teardown(self): @@ -780,7 +784,8 @@ def connect_provider_signals(self, provider_instance): self.sig_pythonpath_changed.connect( provider_instance.python_path_update) self.sig_interpreter_changed.connect( - provider_instance.main_interpreter_changed) + provider_instance.interpreter_changed + ) def _instantiate_and_register_provider( self, Provider: SpyderCompletionProvider): diff --git a/spyder/plugins/completion/providers/languageserver/provider.py b/spyder/plugins/completion/providers/languageserver/provider.py index b0d0f2e0b0a..f91153f50b4 100644 --- a/spyder/plugins/completion/providers/languageserver/provider.py +++ b/spyder/plugins/completion/providers/languageserver/provider.py @@ -126,6 +126,9 @@ class LanguageServerProvider(SpyderCompletionProvider): def __init__(self, parent, config): SpyderCompletionProvider.__init__(self, parent, config) + # To keep track of the current interpreter used for completions + self._interpreter = sys.executable + self.clients = {} self.clients_restart_count = {} self.clients_restart_timers = {} @@ -549,8 +552,9 @@ def python_path_update(self, path_dict, new_path_dict): logger.debug("Update server's sys.path") self.update_lsp_configuration(python_only=True) - @Slot() - def main_interpreter_changed(self): + @Slot(str) + def interpreter_changed(self, interpreter): + self._interpreter = interpreter self.update_lsp_configuration(python_only=True) def file_opened_closed_or_updated(self, filename: str, language: str): @@ -577,11 +581,6 @@ def on_pythonpath_option_update(self, value): if running_under_pytest(): self.update_lsp_configuration(python_only=True) - @on_conf_change(section='main_interpreter', - option=['default', 'custom_interpreter']) - def on_main_interpreter_change(self, option, value): - self.update_lsp_configuration() - def update_lsp_configuration(self, python_only=False): """ Update server configuration after changes done by the user @@ -794,16 +793,9 @@ def generate_python_config(self): # Jedi configuration env_vars = os.environ.copy() # Ensure env is indepependent of PyLSP's env_vars.pop('PYTHONPATH', None) - if self.get_conf('default', section='main_interpreter'): - # If not explicitly set, jedi uses PyLSP's sys.path instead of - # sys.executable's sys.path. This may be a bug in jedi. - environment = sys.executable - else: - environment = self.get_conf('executable', - section='main_interpreter') jedi = { - 'environment': environment, + 'environment': self._interpreter, 'extra_paths': self.get_conf('spyder_pythonpath', section='pythonpath_manager', default=[]), diff --git a/spyder/plugins/maininterpreter/container.py b/spyder/plugins/maininterpreter/container.py index 4265c4cb7f8..1d954684b58 100644 --- a/spyder/plugins/maininterpreter/container.py +++ b/spyder/plugins/maininterpreter/container.py @@ -29,9 +29,14 @@ class MainInterpreterContainer(PluginMainContainer): - sig_interpreter_changed = Signal() + sig_interpreter_changed = Signal(str) """ - Signal to report that the interpreter has changed. + Signal to report that the main interpreter has changed. + + Parameters + ---------- + path: str + Path to the new interpreter. """ sig_environments_updated = Signal(dict) @@ -115,8 +120,9 @@ def on_interpreter_changed(self, option, value): @on_conf_change(option=['executable']) def on_executable_changed(self, value): # announce update - self._update_interpreter(self.get_main_interpreter()) - self.sig_interpreter_changed.emit() + interpreter = self.get_main_interpreter() + self._update_interpreter(interpreter) + self.sig_interpreter_changed.emit(interpreter) def on_close(self): self._get_envs_timer.stop() diff --git a/spyder/plugins/maininterpreter/plugin.py b/spyder/plugins/maininterpreter/plugin.py index 9d3a7feebf6..e1cada5fca2 100644 --- a/spyder/plugins/maininterpreter/plugin.py +++ b/spyder/plugins/maininterpreter/plugin.py @@ -52,6 +52,16 @@ class MainInterpreter(SpyderPluginV2): :py:meth:`spyder.utils.envs.get_list_envs`. """ + sig_interpreter_changed = Signal(str) + """ + Signal to report that the main interpreter has changed. + + Parameters + ---------- + path: str + Path to the new interpreter. + """ + # ---- SpyderPluginV2 API # ------------------------------------------------------------------------- @staticmethod @@ -76,6 +86,7 @@ def on_initialize(self): container.sig_environments_updated.connect( self.sig_environments_updated ) + container.sig_interpreter_changed.connect(self.sig_interpreter_changed) # Validate that the custom interpreter from the previous session # still exists From ae49d4677b088668eceb8eda919a72f368f98693 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 20 Aug 2024 21:48:01 -0500 Subject: [PATCH 2/5] IPython console: Don't register console env actions We don't need to do that because those actions are auto-generated every time the menu they belong to is shown. --- spyder/plugins/ipythonconsole/widgets/main_widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index 30ee43b6861..f182e513936 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -1210,7 +1210,8 @@ def _update_environment_menu(self): path_to_interpreter ) ), - overwrite=True + overwrite=True, + register_action=False, ) # Add default env as the first entry in the menu From c4f57793d73b710732a601669bc7d30c89c41f24 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 21 Aug 2024 10:35:12 -0500 Subject: [PATCH 3/5] Main interpreter: Log when there are changes to the main interpreter --- spyder/plugins/maininterpreter/container.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spyder/plugins/maininterpreter/container.py b/spyder/plugins/maininterpreter/container.py index 1d954684b58..8a2f2b7a813 100644 --- a/spyder/plugins/maininterpreter/container.py +++ b/spyder/plugins/maininterpreter/container.py @@ -8,6 +8,7 @@ """Main interpreter container.""" # Standard library imports +import logging import os import os.path as osp import sys @@ -27,6 +28,9 @@ from spyder.utils.workers import WorkerManager +logger = logging.getLogger(__name__) + + class MainInterpreterContainer(PluginMainContainer): sig_interpreter_changed = Signal(str) @@ -217,6 +221,7 @@ def _finish_updating_envs(self, worker, output, error): def _update_interpreter(self, interpreter=None): """Set main interpreter and update information.""" if interpreter: + logger.debug(f"Main interpreter changed to {interpreter}") self._interpreter = interpreter if self._interpreter not in self.path_to_env: From 210faf0d4bfe55e29a1bd784eefd702c61491199 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 21 Aug 2024 11:37:07 -0500 Subject: [PATCH 4/5] Sync the current IPython console env with the one used for completions - Also, add logs to know when the console and completions interpreters change. - And make a signal in Completion plugin private because it doesn't need to be exposed publicly. --- spyder/api/shellconnect/status.py | 2 +- spyder/plugins/completion/plugin.py | 44 ++++++++++++++----- .../providers/languageserver/provider.py | 22 ++++++++-- spyder/plugins/ipythonconsole/plugin.py | 20 ++++++++- .../ipythonconsole/widgets/main_widget.py | 14 ++++++ .../plugins/ipythonconsole/widgets/status.py | 17 +++++++ 6 files changed, 101 insertions(+), 18 deletions(-) diff --git a/spyder/api/shellconnect/status.py b/spyder/api/shellconnect/status.py index 586ff624b36..bb3bd9ad87c 100644 --- a/spyder/api/shellconnect/status.py +++ b/spyder/api/shellconnect/status.py @@ -62,8 +62,8 @@ def set_shellwidget(self, shellwidget): self.hide() else: self.show() - self.update_status(status) self.current_shellwidget = shellwidget + self.update_status(status) def add_shellwidget(self, shellwidget): """Actions to take when adding a shellwidget.""" diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 0745b3157d1..e519b58de24 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -78,10 +78,11 @@ class CompletionPlugin(SpyderPluginV2): CONF_SECTION = 'completions' REQUIRES = [Plugins.Preferences] OPTIONAL = [ + Plugins.MainInterpreter, Plugins.MainMenu, + Plugins.IPythonConsole, Plugins.PythonpathManager, Plugins.StatusBar, - Plugins.MainInterpreter, ] CONF_FILE = False @@ -135,10 +136,10 @@ class CompletionPlugin(SpyderPluginV2): New PythonPath settings. """ - sig_interpreter_changed = Signal(str) + _sig_interpreter_changed = Signal(str) """ - This signal is used to handle changes in the Python interpreter done by - other plugins. + This private signal is used to handle changes in the Python interpreter + done by other plugins. Parameters ---------- @@ -281,9 +282,13 @@ def on_preferences_available(self): @on_plugin_available(plugin=Plugins.MainInterpreter) def on_maininterpreter_available(self): maininterpreter = self.get_plugin(Plugins.MainInterpreter) - maininterpreter.sig_interpreter_changed.connect( - self.sig_interpreter_changed - ) + + # This will allow people to change the interpreter used for completions + # if they disable the IPython console. + if not self.is_plugin_enabled(Plugins.IPythonConsole): + maininterpreter.sig_interpreter_changed.connect( + self._sig_interpreter_changed + ) @on_plugin_available(plugin=Plugins.StatusBar) def on_statusbar_available(self): @@ -310,6 +315,13 @@ def on_pythonpath_manager_available(self): pythonpath_manager.sig_pythonpath_changed.connect( self.sig_pythonpath_changed) + @on_plugin_available(plugin=Plugins.IPythonConsole) + def on_ipython_console_available(self): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + ipyconsole.sig_interpreter_changed.connect( + self._sig_interpreter_changed + ) + @on_plugin_teardown(plugin=Plugins.Preferences) def on_preferences_teardown(self): preferences = self.get_plugin(Plugins.Preferences) @@ -318,9 +330,12 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.MainInterpreter) def on_maininterpreter_teardown(self): maininterpreter = self.get_plugin(Plugins.MainInterpreter) - maininterpreter.sig_interpreter_changed.disconnect( - self.sig_interpreter_changed - ) + + # We only connect to this signal if the IPython console is not enabled + if not self.is_plugin_enabled(Plugins.IPythonConsole): + maininterpreter.sig_interpreter_changed.disconnect( + self._sig_interpreter_changed + ) @on_plugin_teardown(plugin=Plugins.StatusBar) def on_statusbar_teardown(self): @@ -359,6 +374,13 @@ def on_pythonpath_manager_teardown(self): pythonpath_manager.sig_pythonpath_changed.disconnect( self.sig_pythonpath_changed) + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipython_console_teardowm(self): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + ipyconsole.sig_interpreter_changed.disconnect( + self._sig_interpreter_changed + ) + # ---- Public API def stop_all_providers(self): """Stop all running completion providers.""" @@ -783,7 +805,7 @@ def connect_provider_signals(self, provider_instance): self.sig_pythonpath_changed.connect( provider_instance.python_path_update) - self.sig_interpreter_changed.connect( + self._sig_interpreter_changed.connect( provider_instance.interpreter_changed ) diff --git a/spyder/plugins/completion/providers/languageserver/provider.py b/spyder/plugins/completion/providers/languageserver/provider.py index f91153f50b4..e5d9f90e987 100644 --- a/spyder/plugins/completion/providers/languageserver/provider.py +++ b/spyder/plugins/completion/providers/languageserver/provider.py @@ -20,6 +20,7 @@ from qtpy.QtCore import Signal, Slot, QTimer from qtpy.QtWidgets import QMessageBox from qtpy import PYSIDE2, PYSIDE6 +from superqt.utils import qdebounced # Local imports from spyder.api.config.decorators import on_conf_change @@ -552,10 +553,23 @@ def python_path_update(self, path_dict, new_path_dict): logger.debug("Update server's sys.path") self.update_lsp_configuration(python_only=True) - @Slot(str) - def interpreter_changed(self, interpreter): - self._interpreter = interpreter - self.update_lsp_configuration(python_only=True) + @qdebounced(timeout=600) + def interpreter_changed(self, interpreter: str): + """ + Handle Python interperter changes from other plugins. + + Notes + ----- + - This method is debounced to prevent sending too many requests to the + server when switching IPython consoles for different envs in quick + succession. + - The timeout corresponds more or less to the time it takes to switch + back and forth between two consoles. + """ + if interpreter != self._interpreter: + logger.debug(f"LSP interpreter changed to {interpreter}") + self._interpreter = interpreter + self.update_lsp_configuration(python_only=True) def file_opened_closed_or_updated(self, filename: str, language: str): self.sig_call_statusbar.emit( diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py index e8b45ebcc61..cd7374b3789 100644 --- a/spyder/plugins/ipythonconsole/plugin.py +++ b/spyder/plugins/ipythonconsole/plugin.py @@ -53,12 +53,12 @@ class IPythonConsole(SpyderDockablePlugin, RunExecutor): Plugins.History, Plugins.MainInterpreter, Plugins.MainMenu, - Plugins.Run, Plugins.Projects, Plugins.PythonpathManager, Plugins.RemoteClient, - Plugins.WorkingDirectory, + Plugins.Run, Plugins.StatusBar, + Plugins.WorkingDirectory, ] TABIFY = [Plugins.History] WIDGET_CLASS = IPythonConsoleWidget @@ -209,6 +209,17 @@ class IPythonConsole(SpyderDockablePlugin, RunExecutor): The new working directory path. """ + sig_interpreter_changed = Signal(str) + """ + This signal is emitted when the interpreter of the active shell widget has + changed. + + Parameters + ---------- + path: str + Path to the new interpreter. + """ + # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------- @staticmethod @@ -227,6 +238,8 @@ def get_icon(cls): def on_initialize(self): widget = self.get_widget() + + # Main widget signals widget.sig_append_to_history_requested.connect( self.sig_append_to_history_requested) widget.sig_switch_to_plugin_requested.connect(self.switch_to_plugin) @@ -244,6 +257,9 @@ def on_initialize(self): widget.sig_help_requested.connect(self.sig_help_requested) widget.sig_current_directory_changed.connect( self.sig_current_directory_changed) + widget.sig_interpreter_changed.connect( + self.sig_interpreter_changed + ) # Run configurations self.cython_editor_run_configuration = { diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index f182e513936..919f1145380 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -250,6 +250,17 @@ class IPythonConsoleWidget(PluginMainWidget, CachedKernelMixin): The new working directory path. """ + sig_interpreter_changed = Signal(str) + """ + This signal is emitted when the interpreter of the active shell widget has + changed. + + Parameters + ---------- + path: str + Path to the new interpreter. + """ + def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent) @@ -373,6 +384,9 @@ def __init__(self, name=None, plugin=None, parent=None): # Create status widgets self.matplotlib_status = MatplotlibStatus(self) self.pythonenv_status = PythonEnvironmentStatus(self) + self.pythonenv_status.sig_interpreter_changed.connect( + self.sig_interpreter_changed + ) # Initial value for the current working directory self._current_working_directory = get_home_dir() diff --git a/spyder/plugins/ipythonconsole/widgets/status.py b/spyder/plugins/ipythonconsole/widgets/status.py index edc30ff326e..ffbc93a74d2 100644 --- a/spyder/plugins/ipythonconsole/widgets/status.py +++ b/spyder/plugins/ipythonconsole/widgets/status.py @@ -8,11 +8,13 @@ # Standard library imports import functools +import logging import sys import textwrap # Third-party imports from IPython.core import release as ipython_release +from qtpy.QtCore import Signal from spyder_kernels.comms.frontendcomm import CommError from spyder_kernels.utils.pythonenv import PythonEnvInfo, PythonEnvType @@ -22,6 +24,9 @@ from spyder.config.base import running_in_ci +logger = logging.getLogger(__name__) + + class MatplotlibStatus(ShellConnectStatusBarWidget): """Status bar widget for current Matplotlib backend.""" @@ -170,6 +175,8 @@ class PythonEnvironmentStatus(ShellConnectStatusBarWidget): ID = 'pythonenv_status' CONF_SECTION = 'ipython_console' + sig_interpreter_changed = Signal(str) + def __init__(self, parent): self._current_env_info: PythonEnvInfo | None = None super().__init__(parent) @@ -183,6 +190,16 @@ def get_tooltip(self): # ------------------------------------------------------------------------- def update_status(self, env_info: dict): """Update env info.""" + if ( + # There's no need to emit this signal for remote consoles because + # other plugins can only react to local interpreter changes. + not self.current_shellwidget.is_remote() + and env_info != self._current_env_info + ): + new_interpreter = env_info["path"] + logger.debug(f"Console interpreter changed to {new_interpreter}") + self.sig_interpreter_changed.emit(new_interpreter) + self._current_env_info = env_info if env_info["env_type"] == PythonEnvType.Conda: From 22873aa009f39bd0f388fa6c4cb02bc0a1c4d6eb Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 21 Aug 2024 12:07:07 -0500 Subject: [PATCH 5/5] Testing: Fix tests related to completions in different Jedi environments --- .github/scripts/install.sh | 31 ++++++++------ spyder/app/tests/test_mainwindow.py | 40 +++++++++++-------- .../codeeditor/tests/test_introspection.py | 20 ++++------ 3 files changed, 49 insertions(+), 42 deletions(-) diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index e533a12261a..41cda1bbee2 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -1,5 +1,21 @@ #!/bin/bash -ex +# Auxiliary functions +install_spyder_kernels() { + echo "Installing subrepo version of spyder-kernels in "$1"..." + + pushd external-deps/spyder-kernels + + if [ "$OS" = "win" ]; then + # `conda run` fails on Windows without a clear reason + /c/Miniconda/envs/"$1"/python -m pip install -q . + else + conda run -n "$1" python -m pip install -q . + fi + + popd +} + # Install gdb if [ "$USE_GDB" = "true" ]; then micromamba install gdb -c conda-forge -q -y @@ -65,23 +81,12 @@ fi # Create environment for Jedi environment tests conda create -n jedi-test-env -q -y python=3.9 flask +install_spyder_kernels jedi-test-env conda list -n jedi-test-env # Create environment to test conda env activation before launching a kernel conda create -n spytest-ž -q -y -c conda-forge python=3.9 - -# Install subrepo version of Spyder-kernels in that env -pushd external-deps/spyder-kernels - -if [ "$OS" = "win" ]; then - # `conda run` fails on Windows without a clear reason - /c/Miniconda/envs/spytest-ž/python -m pip install . -else - conda run -n spytest-ž python -m pip install . -fi - -popd - +install_spyder_kernels spytest-ž conda list -n spytest-ž # Install pyenv on Linux systems diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py index 7b214b4988e..9a126ea75aa 100644 --- a/spyder/app/tests/test_mainwindow.py +++ b/spyder/app/tests/test_mainwindow.py @@ -3344,10 +3344,18 @@ def test_preferences_shortcut_reset_regression(main_window, qtbot): @pytest.mark.order(1) @flaky(max_runs=3) +@pytest.mark.order(before="test_PYTHONPATH_in_consoles") @pytest.mark.skipif(not is_anaconda(), reason='Only works with Anaconda') @pytest.mark.skipif(not running_in_ci(), reason='Only works on CIs') -def test_preferences_change_interpreter(qtbot, main_window): - """Test that on main interpreter change signal is emitted.""" +@pytest.mark.skipif( + not sys.platform.startswith("linux"), + reason="Only works on Linux on CIs but passes locally" +) +def test_change_lsp_interpreter(qtbot, main_window): + """ + Test that the LSP Python interpreter changes when switching consoles for + different envs. + """ # Wait until the window is fully up shell = main_window.ipyconsole.get_current_shellwidget() qtbot.waitUntil( @@ -3355,33 +3363,31 @@ def test_preferences_change_interpreter(qtbot, main_window): timeout=SHELL_TIMEOUT, ) - # Check original pyls configuration + # Check original pylsp configuration lsp = main_window.completions.get_provider('lsp') config = lsp.generate_python_config() jedi = config['configurations']['pylsp']['plugins']['jedi'] assert jedi['environment'] == sys.executable assert jedi['extra_paths'] == [] - # Get conda env to use - conda_env = get_list_conda_envs()['Conda: jedi-test-env'][0] + # Get new interpreter to use + new_interpreter = get_list_conda_envs()['Conda: jedi-test-env'][0] - # Change main interpreter on preferences - dlg, index, page = preferences_dialog_helper( - qtbot, main_window, 'main_interpreter' - ) - page.cus_exec_radio.radiobutton.setChecked(True) - page.cus_exec_combo.combobox.setCurrentText(conda_env) - - main_interpreter = main_window.main_interpreter + # Create console for new interpreter + ipyconsole = main_window.ipyconsole with qtbot.waitSignal( - main_interpreter.sig_interpreter_changed, timeout=5000, raising=True + ipyconsole.sig_interpreter_changed, timeout=SHELL_TIMEOUT, raising=True ): - dlg.ok_btn.animateClick() + ipyconsole.get_widget().create_environment_client( + "jedi-test-env", + new_interpreter + ) - # Check updated pyls configuration + # Check updated pylsp configuration + qtbot.wait(1000) # Account for debounced timeout when setting interpreter config = lsp.generate_python_config() jedi = config['configurations']['pylsp']['plugins']['jedi'] - assert jedi['environment'] == conda_env + assert jedi['environment'] == new_interpreter assert jedi['extra_paths'] == [] diff --git a/spyder/plugins/editor/widgets/codeeditor/tests/test_introspection.py b/spyder/plugins/editor/widgets/codeeditor/tests/test_introspection.py index 29181173521..e10c21db858 100644 --- a/spyder/plugins/editor/widgets/codeeditor/tests/test_introspection.py +++ b/spyder/plugins/editor/widgets/codeeditor/tests/test_introspection.py @@ -35,16 +35,11 @@ LOCATION = osp.realpath(osp.join(os.getcwd(), osp.dirname(__file__))) -def set_executable_config_helper(completion_plugin, executable=None): +def set_executable_helper(completion_plugin, executable=None): if executable is None: - completion_plugin.set_conf('executable', sys.executable, - 'main_interpreter') - completion_plugin.set_conf('default', True, 'main_interpreter') - completion_plugin.set_conf('custom', False, 'main_interpreter') + completion_plugin._sig_interpreter_changed.emit(sys.executable) else: - completion_plugin.set_conf('executable', executable, - 'main_interpreter') - completion_plugin.set_conf('default', False, 'main_interpreter') + completion_plugin._sig_interpreter_changed.emit(executable) @pytest.mark.order(1) @@ -1019,8 +1014,9 @@ def spam(): @flaky(max_runs=20) @pytest.mark.skipif(not is_anaconda(), reason='Requires conda to work') @pytest.mark.skipif(not running_in_ci(), reason="Only meant for CIs") -@pytest.mark.skipif(not sys.platform.startswith('linux'), - reason="Works reliably on Linux") +@pytest.mark.skipif( + not sys.platform.startswith('linux'), reason="Works reliably on Linux" +) def test_completions_environment(completions_codeeditor, qtbot, tmpdir): """ Exercise code completions when using another Jedi environment, i.e. a @@ -1045,7 +1041,7 @@ def test_completions_environment(completions_codeeditor, qtbot, tmpdir): # Set interpreter that has Flask and check we can provide completions for # it code_editor.set_text('') - set_executable_config_helper(completion_plugin, py_exe) + set_executable_helper(completion_plugin, py_exe) completion_plugin.after_configuration_update([]) qtbot.wait(5000) @@ -1059,7 +1055,7 @@ def test_completions_environment(completions_codeeditor, qtbot, tmpdir): assert "flask" in [x['label'] for x in sig.args[0]] assert code_editor.toPlainText() == 'import flask' - set_executable_config_helper(completion_plugin) + set_executable_helper(completion_plugin) completion_plugin.after_configuration_update([]) qtbot.wait(5000)