Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Improve validation for the right Spyder-kernels version #19074

Merged
merged 16 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions external-deps/spyder-kernels/.gitrepo

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions external-deps/spyder-kernels/spyder_kernels/console/shell.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions spyder/plugins/ipythonconsole/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@

IPython Console plugin based on QtConsole
"""

# Required version of Spyder-kernels
impact27 marked this conversation as resolved.
Show resolved Hide resolved
SPYDER_KERNELS_MIN_VERSION = '2.3.0'
SPYDER_KERNELS_MAX_VERSION = '2.4.0'
SPYDER_KERNELS_VERSION = (
f'>={SPYDER_KERNELS_MIN_VERSION};<{SPYDER_KERNELS_MAX_VERSION}')
SPYDER_KERNELS_CONDA = (
f'conda install spyder&#45;kernels={SPYDER_KERNELS_MIN_VERSION[:-2]}')
SPYDER_KERNELS_PIP = (
f'pip install spyder&#45;kernels=={SPYDER_KERNELS_MIN_VERSION[:-1]}*')


class SpyderKernelError(RuntimeError):
"""Error to be shown in client."""
pass
34 changes: 34 additions & 0 deletions spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -2387,5 +2387,39 @@ def test_varexp_magic_dbg_locals(ipyconsole, qtbot):
assert shell._control.toHtml().count('img src') == 1


@pytest.mark.skipif(os.name == 'nt', reason="Fails on windows")
def test_old_kernel_version(ipyconsole, qtbot):
"""
Check that an error is shown when an version of spyder-kernels is used.
"""
# Set a false _spyder_kernels_version in the cached kernel
w = ipyconsole.get_widget()
# create new client so PYTEST_CURRENT_TEST is the same
w.create_new_client()
# Wait until the window is fully up
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(
lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

kc = w._cached_kernel_properties[-1][2]
kc.start_channels()
kc.execute("get_ipython()._spyder_kernels_version = ('1.0.0', '')")
# Cleanup the kernel_client so it can be used again
kc.stop_channels()
kc._shell_channel = None
kc._iopub_channel = None
kc._stdin_channel = None
kc._hb_channel = None
kc._control_channel = None

# Create new client
w.create_new_client()
client = w.get_current_client()

# Make sure an error is shown
qtbot.waitUntil(lambda: client.error_text is not None)
assert '1.0.0' in client.error_text


if __name__ == "__main__":
pytest.main()
42 changes: 41 additions & 1 deletion spyder/plugins/ipythonconsole/utils/kernelspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,43 @@

# Local imports
from spyder.api.config.mixins import SpyderConfigurationAccessor
from spyder.api.translations import get_translation
from spyder.config.base import (get_safe_mode, is_pynsist, running_in_mac_app,
running_under_pytest)
from spyder.plugins.ipythonconsole import (
SPYDER_KERNELS_CONDA, SPYDER_KERNELS_PIP, SpyderKernelError)
from spyder.utils.conda import (add_quotes, get_conda_activation_script,
get_conda_env_path, is_conda_env)
from spyder.utils.environ import clean_env
from spyder.utils.misc import get_python_executable
from spyder.utils.programs import is_python_interpreter
from spyder.utils.programs import is_python_interpreter, is_module_installed

# Constants
HERE = os.path.abspath(os.path.dirname(__file__))
logger = logging.getLogger(__name__)


# Localization
_ = get_translation('spyder')

ERROR_SPYDER_KERNEL_INSTALLED = _(
"The Python environment or installation whose interpreter is located at"
"<pre>"
" <tt>{0}</tt>"
"</pre>"
"doesn't have the <tt>spyder-kernels</tt> module installed. Without this "
"module is not possible for Spyder to create a console for you.<br><br>"
"You can install it by activating your environment (if necessary) and "
"running in a system terminal:"
"<pre>"
" <tt>{1}</tt>"
"</pre>"
"or"
"<pre>"
" <tt>{2}</tt>"
"</pre>")


def is_different_interpreter(pyexec):
"""Check that pyexec is a different interpreter from sys.executable."""
executable_validation = osp.basename(pyexec).startswith('python')
Expand Down Expand Up @@ -59,6 +83,13 @@ def get_activation_script(quote=False):
return script_path


def has_spyder_kernels(pyexec):
"""Check if env has spyder kernels."""
return is_module_installed(
'spyder_kernels',
interpreter=pyexec)


HERE = osp.dirname(os.path.realpath(__file__))


Expand Down Expand Up @@ -86,6 +117,15 @@ def argv(self):
pyexec = get_python_executable()
else:
pyexec = self.get_conf('executable', section='main_interpreter')
if not has_spyder_kernels(pyexec):
raise SpyderKernelError(
ERROR_SPYDER_KERNEL_INSTALLED.format(
pyexec,
SPYDER_KERNELS_CONDA,
SPYDER_KERNELS_PIP
)
)
return
if not is_python_interpreter(pyexec):
pyexec = get_python_executable()
self.set_conf('executable', '', section='main_interpreter')
Expand Down
62 changes: 9 additions & 53 deletions spyder/plugins/ipythonconsole/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from spyder.api.widgets.menus import MENU_SEPARATOR
from spyder.config.base import (
get_conf_path, get_home_dir, running_under_pytest)
from spyder.plugins.ipythonconsole import SpyderKernelError
from spyder.plugins.ipythonconsole.utils.kernelspec import SpyderKernelSpec
from spyder.plugins.ipythonconsole.utils.manager import SpyderKernelManager
from spyder.plugins.ipythonconsole.utils.client import SpyderKernelClient
Expand All @@ -58,17 +59,6 @@
# ---- Constants
# =============================================================================
MAIN_BG_COLOR = QStylePalette.COLOR_BACKGROUND_1
SPYDER_KERNELS_MIN_VERSION = '2.3.0'
SPYDER_KERNELS_MAX_VERSION = '2.4.0'
SPYDER_KERNELS_VERSION = (
f'>={SPYDER_KERNELS_MIN_VERSION};<{SPYDER_KERNELS_MAX_VERSION}')
SPYDER_KERNELS_VERSION_MSG = _(
'>= {0} and < {1}').format(
SPYDER_KERNELS_MIN_VERSION, SPYDER_KERNELS_MAX_VERSION)
SPYDER_KERNELS_CONDA = (
f'conda install spyder&#45;kernels={SPYDER_KERNELS_MIN_VERSION[:-2]}')
SPYDER_KERNELS_PIP = (
f'pip install spyder&#45;kernels=={SPYDER_KERNELS_MIN_VERSION[:-1]}*')


class IPythonConsoleWidgetActions:
Expand Down Expand Up @@ -1486,42 +1476,6 @@ def create_new_client(self, give_focus=True, filename='', is_cython=False,
client.show_kernel_error(error_msg)
return

# Check if ipykernel is present in the external interpreter.
# Else we won't be able to create a client
if not self.get_conf('default', section='main_interpreter'):
pyexec = self.get_conf('executable', section='main_interpreter')
has_spyder_kernels = programs.is_module_installed(
'spyder_kernels',
interpreter=pyexec,
version=SPYDER_KERNELS_VERSION)
if not has_spyder_kernels and not running_under_pytest():
client.show_kernel_error(
_("The Python environment or installation whose "
"interpreter is located at"
"<pre>"
" <tt>{0}</tt>"
"</pre>"
"doesn't have the <tt>spyder-kernels</tt> module or the "
"right version of it installed ({1}). "
"Without this module is not possible for Spyder to "
"create a console for you.<br><br>"
"You can install it by activating your environment (if "
"necessary) and then running in a system terminal:"
"<pre>"
" <tt>{2}</tt>"
"</pre>"
"or"
"<pre>"
" <tt>{3}</tt>"
"</pre>").format(
pyexec,
SPYDER_KERNELS_VERSION_MSG,
SPYDER_KERNELS_CONDA,
SPYDER_KERNELS_PIP
)
)
return

self.connect_client_to_kernel(client, km, kc)
if client.shellwidget.kernel_manager is None:
return
Expand Down Expand Up @@ -1653,16 +1607,14 @@ def create_client_for_kernel(self, connection_file, hostname, sshkey,
# Assign kernel manager and client to shellwidget
kernel_client.start_channels()
shellwidget = client.shellwidget
shellwidget.set_kernel_client_and_manager(
kernel_client, kernel_manager)
shellwidget.sig_exception_occurred.connect(
self.sig_exception_occurred)

if not known_spyder_kernel:
impact27 marked this conversation as resolved.
Show resolved Hide resolved
shellwidget.sig_is_spykernel.connect(
self.connect_external_spyder_kernel)
shellwidget.check_spyder_kernel()

shellwidget.set_kernel_client_and_manager(
kernel_client, kernel_manager)
shellwidget.sig_exception_occurred.connect(
self.sig_exception_occurred)
self.sig_shellwidget_created.emit(shellwidget)
kernel_client.stopped_channels.connect(
lambda: self.sig_shellwidget_deleted.emit(shellwidget))
Expand Down Expand Up @@ -2131,6 +2083,8 @@ def create_kernel_manager_and_kernel_client(self, connection_file,
config=None,
autorestart=True,
)
except SpyderKernelError as e:
return (e.args[0], None)
except Exception:
error_msg = _("The error is:<br><br>"
"<tt>{}</tt>").format(traceback.format_exc())
Expand All @@ -2143,6 +2097,8 @@ def create_kernel_manager_and_kernel_client(self, connection_file,
kernel_manager.start_kernel(stderr=stderr_handle,
stdout=stdout_handle,
env=kernel_spec.env)
except SpyderKernelError as e:
return (e.args[0], None)
except Exception:
error_msg = _("The error is:<br><br>"
"<tt>{}</tt>").format(traceback.format_exc())
Expand Down
Loading