From f82209476a94e6b44693a8abf3f12eb8477a5dc5 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Sep 2018 17:39:25 -0500 Subject: [PATCH 01/29] Console: Add a method to get its connection file --- spyder_kernels/console/kernel.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index 70f86fce..3f5a3e79 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -155,3 +155,8 @@ def _load_autoreload_magic(self): from IPython.core.getipython import get_ipython get_ipython().run_line_magic('reload_ext', 'autoreload') get_ipython().run_line_magic('autoreload', '2') + + def _get_connection_file(self): + """Get kernel's connection file.""" + from ipykernel import get_connection_file + return get_connection_file() From d656319f529313625a41b12f68c69818946673dd Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Sep 2018 17:41:07 -0500 Subject: [PATCH 02/29] IPdb: Add a kernelspec class to easily start it --- spyder_kernels/ipdb/kernelspec.py | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 spyder_kernels/ipdb/kernelspec.py diff --git a/spyder_kernels/ipdb/kernelspec.py b/spyder_kernels/ipdb/kernelspec.py new file mode 100644 index 00000000..f100a2bf --- /dev/null +++ b/spyder_kernels/ipdb/kernelspec.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (c) 2009- Spyder Kernels Contributors +# +# Licensed under the terms of the MIT License +# (see spyder_kernels/__init__.py for details) +# ----------------------------------------------------------------------------- + +""" +Kernelspec for IPdb kernels +""" + +import sys + +from IPython import get_ipython +from jupyter_client.kernelspec import KernelSpec + +from spyder_kernels.py3compat import (PY2, iteritems, to_text_string, + to_binary_string) + + +class IPdbKernelSpec(KernelSpec): + """Kernelspec for IPdb kernels.""" + + @property + def argv(self): + """Command to start the kernel.""" + + # Command used to start kernels + kernel_cmd = [ + sys.executable, + '-m', + 'spyder_kernels.ipdb', + '-f', + '{connection_file}' + ] + + return kernel_cmd + + @property + def env(self): + """Env vars for the kernel.""" + + connection_file = get_ipython().kernel._get_connection_file() + + env_vars = { + 'SPY_CONSOLE_CONNECTION_FILE': connection_file + } + + # Making all env_vars strings + for key,var in iteritems(env_vars): + if PY2: + # Try to convert vars first to utf-8. + try: + unicode_var = to_text_string(var) + except UnicodeDecodeError: + # TODO: Fix this by moving to_unicode_from_fs + # from Spyder + + # If that fails, try to use the file system + # encoding because one of our vars is our + # PYTHONPATH, and that contains file system + # directories + #try: + # unicode_var = to_unicode_from_fs(var) + #except: + # If that also fails, make the var empty + # to be able to start Spyder. + # See https://stackoverflow.com/q/44506900/438386 + # for details. + # unicode_var = '' + pass + env_vars[key] = to_binary_string(unicode_var, + encoding='utf-8') + else: + env_vars[key] = to_text_string(var) + return env_vars From 207f3cc76e839d6f036b121bf2f946d2a9e5d873 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Sep 2018 17:42:26 -0500 Subject: [PATCH 03/29] SpyderPdb: Add a method to start an IPdb kernel --- spyder_kernels/ipdb/spyderpdb.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/spyder_kernels/ipdb/spyderpdb.py b/spyder_kernels/ipdb/spyderpdb.py index 9e90dbec..7bb9a61c 100644 --- a/spyder_kernels/ipdb/spyderpdb.py +++ b/spyder_kernels/ipdb/spyderpdb.py @@ -5,20 +5,25 @@ # Licensed under the terms of the MIT License # (see spyder_kernels/__init__.py for details) # ----------------------------------------------------------------------------- + # Standard library import from __future__ import print_function - import bdb import pdb import os.path as osp -# local library imports +# Third-party imports +from IPython.core.debugger import Pdb as ipyPdb +from IPython import get_ipython +from jupyter_client.manager import KernelManager + +# Local library imports +from spyder_kernels.ipdb.kernelspec import IPdbKernelSpec from spyder_kernels.py3compat import PY2 from spyder_kernels.utils.misc import monkeypatch_method + # Use ipydb as the debugger to patch on IPython consoles -from IPython.core.debugger import Pdb as ipyPdb -from IPython import get_ipython pdb.Pdb = ipyPdb @@ -113,6 +118,12 @@ def notify_spyder(self, frame): kernel._pdb_step = step kernel.publish_pdb_state() + def start_ipdb_kernel(self): + """Start IPdb kernel.""" + self.ipdb_manager = KernelManager() + self.ipdb_manager._kernel_spec = IPdbKernelSpec() + self.ipdb_manager.start_kernel() + @monkeypatch_method(pdb.Pdb, 'Pdb') def __init__(self, completekey='tab', stdin=None, stdout=None, From 7ebf6342cb415ef959d40309d086643ccb8f8241 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Sep 2018 21:48:07 -0500 Subject: [PATCH 04/29] IPdb: Pass to its kernelspec the console kernel connection info - This is much more robust than passing the connection file. --- spyder_kernels/console/kernel.py | 9 ++++++--- spyder_kernels/ipdb/kernelspec.py | 33 +++++++++++-------------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index 3f5a3e79..a301441c 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -11,6 +11,7 @@ """ # Standard library imports +import json import pickle # Local imports @@ -156,7 +157,9 @@ def _load_autoreload_magic(self): get_ipython().run_line_magic('reload_ext', 'autoreload') get_ipython().run_line_magic('autoreload', '2') - def _get_connection_file(self): - """Get kernel's connection file.""" + def _get_connection_info(self): + """Get kernel's connection info.""" from ipykernel import get_connection_file - return get_connection_file() + with open(get_connection_file()) as f: + info = json.load(f) + return info diff --git a/spyder_kernels/ipdb/kernelspec.py b/spyder_kernels/ipdb/kernelspec.py index f100a2bf..5c40e6bc 100644 --- a/spyder_kernels/ipdb/kernelspec.py +++ b/spyder_kernels/ipdb/kernelspec.py @@ -41,35 +41,24 @@ def argv(self): def env(self): """Env vars for the kernel.""" - connection_file = get_ipython().kernel._get_connection_file() + info = get_ipython().kernel._get_connection_info() env_vars = { - 'SPY_CONSOLE_CONNECTION_FILE': connection_file + 'SPY_CONSOLE_SHELL_PORT': info['shell_port'], + 'SPY_CONSOLE_IOPUB_PORT': info['iopub_port'], + 'SPY_CONSOLE_STDIN_PORT': info['stdin_port'], + 'SPY_CONSOLE_CONTROL_PORT': info['control_port'], + 'SPY_CONSOLE_HB_PORT': info['hb_port'], + 'SPY_CONSOLE_IP': info['ip'], + 'SPY_CONSOLE_KEY': info['key'], + 'SPY_CONSOLE_TRANSPORT': info['transport'], + 'SPY_CONSOLE_SIGNATURE_SCHEME': info['signature_scheme'], } # Making all env_vars strings for key,var in iteritems(env_vars): if PY2: - # Try to convert vars first to utf-8. - try: - unicode_var = to_text_string(var) - except UnicodeDecodeError: - # TODO: Fix this by moving to_unicode_from_fs - # from Spyder - - # If that fails, try to use the file system - # encoding because one of our vars is our - # PYTHONPATH, and that contains file system - # directories - #try: - # unicode_var = to_unicode_from_fs(var) - #except: - # If that also fails, make the var empty - # to be able to start Spyder. - # See https://stackoverflow.com/q/44506900/438386 - # for details. - # unicode_var = '' - pass + unicode_var = to_text_string(var) env_vars[key] = to_binary_string(unicode_var, encoding='utf-8') else: From 9badef30b95c340a36fbecb51d278317a4f79a81 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Sep 2018 21:54:24 -0500 Subject: [PATCH 05/29] IPdb: Create a kernel client connected to the console kernel --- spyder_kernels/ipdb/kernel.py | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 2c1e796b..b844f52e 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -15,6 +15,7 @@ """ import functools +import os import sys from ipykernel.eventloops import enable_gui @@ -23,6 +24,7 @@ from IPython.core.interactiveshell import InteractiveShell from IPython.core.debugger import BdbQuit_excepthook from IPython.utils.tokenutil import token_at_cursor +from jupyter_client.blocking.client import BlockingKernelClient from metakernel import MetaKernel from spyder_kernels._version import __version__ @@ -131,6 +133,15 @@ def __init__(self, *args, **kwargs): # Add _get_kernel_ builtins._get_kernel_ = self._get_kernel_ + # Create console client + try: + self.console_client = self._create_console_client() + except KeyError: + self.console_client = None + + if self.console_client is not None: + self.console_client.start_channels() + self._remove_unneeded_magics() # --- MetaKernel API @@ -320,3 +331,30 @@ def _show_inline_figures(self): def _get_kernel_(self): """To add _get_kernel_ function to builtins.""" return self + + def _create_console_client(self): + """Create a kernel client connected to the console kernel.""" + # Retrieve connection info from the environment + shell_port = int(os.environ['SPY_CONSOLE_SHELL_PORT']) + iopub_port = int(os.environ['SPY_CONSOLE_IOPUB_PORT']) + stdin_port = int(os.environ['SPY_CONSOLE_STDIN_PORT']) + control_port = int(os.environ['SPY_CONSOLE_CONTROL_PORT']) + hb_port = int(os.environ['SPY_CONSOLE_HB_PORT']) + ip = os.environ['SPY_CONSOLE_IP'] + key = os.environ['SPY_CONSOLE_KEY'] + transport = os.environ['SPY_CONSOLE_TRANSPORT'] + signature_scheme = os.environ['SPY_CONSOLE_SIGNATURE_SCHEME'] + + info = dict(shell_port=shell_port, + iopub_port=iopub_port, + stdin_port=stdin_port, + control_port=control_port, + hb_port=hb_port, + ip=ip, + key=key, + transport=transport, + signature_scheme=signature_scheme) + + client = BlockingKernelClient() + client.load_connection_info(info) + return client From 4dccd5172d849b46f2599b7af6132675e98aba6b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 08:52:12 -0500 Subject: [PATCH 06/29] IPdb: Create its own console kernel client for testing purposes This way the IPdb kernel could stand on its own --- spyder_kernels/ipdb/kernel.py | 68 ++++++++++++++++++++------------ spyder_kernels/ipdb/spyderpdb.py | 6 +++ 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index b844f52e..2f89b63a 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -333,28 +333,46 @@ def _get_kernel_(self): return self def _create_console_client(self): - """Create a kernel client connected to the console kernel.""" - # Retrieve connection info from the environment - shell_port = int(os.environ['SPY_CONSOLE_SHELL_PORT']) - iopub_port = int(os.environ['SPY_CONSOLE_IOPUB_PORT']) - stdin_port = int(os.environ['SPY_CONSOLE_STDIN_PORT']) - control_port = int(os.environ['SPY_CONSOLE_CONTROL_PORT']) - hb_port = int(os.environ['SPY_CONSOLE_HB_PORT']) - ip = os.environ['SPY_CONSOLE_IP'] - key = os.environ['SPY_CONSOLE_KEY'] - transport = os.environ['SPY_CONSOLE_TRANSPORT'] - signature_scheme = os.environ['SPY_CONSOLE_SIGNATURE_SCHEME'] - - info = dict(shell_port=shell_port, - iopub_port=iopub_port, - stdin_port=stdin_port, - control_port=control_port, - hb_port=hb_port, - ip=ip, - key=key, - transport=transport, - signature_scheme=signature_scheme) - - client = BlockingKernelClient() - client.load_connection_info(info) - return client + """Create a kernel client connected to a console kernel.""" + try: + # Retrieve connection info from the environment + shell_port = int(os.environ['SPY_CONSOLE_SHELL_PORT']) + iopub_port = int(os.environ['SPY_CONSOLE_IOPUB_PORT']) + stdin_port = int(os.environ['SPY_CONSOLE_STDIN_PORT']) + control_port = int(os.environ['SPY_CONSOLE_CONTROL_PORT']) + hb_port = int(os.environ['SPY_CONSOLE_HB_PORT']) + ip = os.environ['SPY_CONSOLE_IP'] + key = os.environ['SPY_CONSOLE_KEY'] + transport = os.environ['SPY_CONSOLE_TRANSPORT'] + signature_scheme = os.environ['SPY_CONSOLE_SIGNATURE_SCHEME'] + + # Create info dict + info = dict(shell_port=shell_port, + iopub_port=iopub_port, + stdin_port=stdin_port, + control_port=control_port, + hb_port=hb_port, + ip=ip, + key=key, + transport=transport, + signature_scheme=signature_scheme) + + # Create kernel client + kernel_client = BlockingKernelClient() + kernel_client.load_connection_info(info) + kernel_client.start_channels() + except KeyError: + # Create a console kernel to interact with, so this + # kernel can stand on its own. + # *Note*: This is useful for testing purposes only! + from jupyter_client.manager import KernelManager + kernel_manager = KernelManager(kernel_name='spyder_console') + kernel_manager.start_kernel() + kernel_client = kernel_manager.client() + kernel_client.start_channels() + + # Register a Pdb instance so that PdbProxy can work + kernel_client.execute('import pdb; p=pdb.Pdb(); p.init()', + silent=True) + + return kernel_client diff --git a/spyder_kernels/ipdb/spyderpdb.py b/spyder_kernels/ipdb/spyderpdb.py index 7bb9a61c..c8eda280 100644 --- a/spyder_kernels/ipdb/spyderpdb.py +++ b/spyder_kernels/ipdb/spyderpdb.py @@ -11,6 +11,7 @@ import bdb import pdb import os.path as osp +import sys # Third-party imports from IPython.core.debugger import Pdb as ipyPdb @@ -118,6 +119,11 @@ def notify_spyder(self, frame): kernel._pdb_step = step kernel.publish_pdb_state() + def init(self): + """Our own initialization routine.""" + self.reset() + self.setup(sys._getframe().f_back, None) + def start_ipdb_kernel(self): """Start IPdb kernel.""" self.ipdb_manager = KernelManager() From 08f3b7ca7ad4bafceab9c79ebfa677d174c3ffde Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 08:58:41 -0500 Subject: [PATCH 07/29] IPdb: Add PdbProxy to send Pdb commands to Pdb instance in console kernel --- spyder_kernels/ipdb/kernel.py | 47 ++++++++++++--------------------- spyder_kernels/ipdb/pdbproxy.py | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 spyder_kernels/ipdb/pdbproxy.py diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 2f89b63a..aa44e7e5 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -29,7 +29,7 @@ from spyder_kernels._version import __version__ from spyder_kernels.ipdb import backend_inline -from spyder_kernels.ipdb.spyderpdb import SpyderPdb +from spyder_kernels.ipdb.pdbproxy import PdbProxy from spyder_kernels.kernelmixin import BaseKernelMixIn from spyder_kernels.py3compat import builtins from spyder_kernels.utils.module_completion import module_completion @@ -103,21 +103,15 @@ class IPdbKernel(BaseKernelMixIn, MetaKernel): def __init__(self, *args, **kwargs): super(IPdbKernel, self).__init__(*args, **kwargs) - # Instantiate spyder_kernels.ipdb.spyderpdb.SpyderPdb here, - # pass it a phony stdout that provides a dummy - # flush() method and a write() method - # that internally sends data using a function so that it can - # be initialized to use self.send_response() - sys.excepthook = functools.partial(BdbQuit_excepthook, - excepthook=sys.excepthook) - self.debugger = SpyderPdb(stdout=PhonyStdout(self._phony_stdout)) - self.debugger.reset() - self.debugger.setup(sys._getframe().f_back, None) + # Create Pdb proxy + console_kernel_client = self._create_console_kernel_client() + self.debugger = PdbProxy(console_kernel_client) # Completer + # TODO: Move completer to SpyderPdb self.completer = IPdbCompleter( - shell=DummyShell(), - namespace=self._get_current_namespace() + shell=DummyShell(), + namespace={}, #self._get_current_namespace() ) self.completer.limit_to__all__ = False @@ -126,22 +120,15 @@ def __init__(self, *args, **kwargs): line_input_checker=False) # For the %matplotlib magic + # TODO: Remove this? self.ipyshell = InteractiveShell() self.ipyshell.enable_gui = enable_gui self.mpl_gui = None # Add _get_kernel_ + # TODO: Remove this? builtins._get_kernel_ = self._get_kernel_ - # Create console client - try: - self.console_client = self._create_console_client() - except KeyError: - self.console_client = None - - if self.console_client is not None: - self.console_client.start_channels() - self._remove_unneeded_magics() # --- MetaKernel API @@ -149,14 +136,14 @@ def do_execute_direct(self, code): """ Execute code with the debugger. """ - # Process command: - line = self.debugger.precmd(code) - stop = self.debugger.default(line) - stop = self.debugger.postcmd(stop, line) - if stop: - self.debugger.postloop() + # Process command + line = code.strip() + self.debugger.default(line) + self.debugger.postcmd(None, line) - self._show_inline_figures() + # Post command operations + # TODO: Fix inline figures + # self._show_inline_figures() def do_is_complete(self, code): """ @@ -332,7 +319,7 @@ def _get_kernel_(self): """To add _get_kernel_ function to builtins.""" return self - def _create_console_client(self): + def _create_console_kernel_client(self): """Create a kernel client connected to a console kernel.""" try: # Retrieve connection info from the environment diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py new file mode 100644 index 00000000..a140afaf --- /dev/null +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (c) 2009- Spyder Kernels Contributors +# +# Licensed under the terms of the MIT License +# (see spyder_kernels/__init__.py for details) +# ----------------------------------------------------------------------------- + +""" +Proxy to execute Pdb commands in a Pdb instance running in a +different kernel. +""" + +class PdbProxy(object): + + remote_pdb_obj = 'get_ipython().kernel._pdb_obj' + + def __init__(self, kernel_client): + self.kernel_client = kernel_client + + def _execute(self, command, interactive=False): + """Execute command in a remote Pdb instance.""" + if interactive: + kc_exec = self.kernel_client.execute_interactive + else: + kc_exec = self.kernel_client.execute + + pdb_cmd = self.remote_pdb_obj + '.' + command + if interactive: + kc_exec(pdb_cmd, store_history=False) + else: + kc_exec(pdb_cmd, silent=True) + + def default(self, line): + self._execute('default("{}")'.format(line), interactive=True) + + def postcmd(self, stop, line): + self._execute('postcmd(None, "{}")'.format(line)) + + def do_break(self, arg=None, temporary=0): + if arg: + self._execute('do_break("{}", {})'.format(arg, temporary), + interactive=True) + else: + self._execute('do_break(None, {})'.format(temporary), + interactive=True) From 925c9469ad91fc32459006da061952f0fb90970d Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 17:58:40 -0500 Subject: [PATCH 08/29] IPdb: Make code completions work again Completions are obtained from the remote Pdb instance now --- spyder_kernels/ipdb/kernel.py | 74 +++----------------------------- spyder_kernels/ipdb/pdbproxy.py | 37 +++++++++++++--- spyder_kernels/ipdb/spyderpdb.py | 62 +++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 76 deletions(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index aa44e7e5..3613564e 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -19,7 +19,6 @@ import sys from ipykernel.eventloops import enable_gui -from IPython.core.completer import IPCompleter from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.interactiveshell import InteractiveShell from IPython.core.debugger import BdbQuit_excepthook @@ -50,33 +49,6 @@ def close(self): pass -class DummyShell(object): - """Dummy shell to pass to IPCompleter.""" - - @property - def magics_manager(self): - """ - Create a dummy magics manager with the interface - expected by IPCompleter. - """ - class DummyMagicsManager(object): - def lsmagic(self): - return {'line': {}, 'cell': {}} - - return DummyMagicsManager() - - -class IPdbCompleter(IPCompleter): - """ - Subclass of IPCompleter without file completions so they don't - interfere with the ones provided by MetaKernel. - """ - - def file_matches(self, text): - """Return and empty list to deactivate file matches.""" - return [] - - class IPdbKernel(BaseKernelMixIn, MetaKernel): implementation = "IPdb Kernel" implementation_version = __version__ @@ -107,21 +79,12 @@ def __init__(self, *args, **kwargs): console_kernel_client = self._create_console_kernel_client() self.debugger = PdbProxy(console_kernel_client) - # Completer - # TODO: Move completer to SpyderPdb - self.completer = IPdbCompleter( - shell=DummyShell(), - namespace={}, #self._get_current_namespace() - ) - self.completer.limit_to__all__ = False - # To detect if a line is complete self.input_transformer_manager = IPythonInputSplitter( line_input_checker=False) # For the %matplotlib magic - # TODO: Remove this? - self.ipyshell = InteractiveShell() + self.ipyshell = InteractiveShell().instance() self.ipyshell.enable_gui = enable_gui self.mpl_gui = None @@ -194,18 +157,16 @@ def do_inspect(self, code, cursor_pos, detail_level=0): return reply_content def get_completions(self, info): - """ - Get completions from kernel based on info dict. - """ + """Get code completions.""" code = info["code"] - # Update completer namespace before performing the - # completion - self.completer.namespace = self._get_current_namespace() if code.startswith('import') or code.startswith('from'): matches = module_completion(code) else: - matches = self.completer.complete(text=None, line_buffer=code)[1] + # We need to ask for completions twice to get the + # right completions through user_expressions + for _ in range(2): + matches = self.debugger._get_completions(code) return matches @@ -258,28 +219,6 @@ def _remove_unneeded_magics(self): except: pass - def _get_current_namespace(self, with_magics=False): - """Get current namespace.""" - glbs = self.debugger.curframe.f_globals - lcls = self.debugger.curframe.f_locals - ns = {} - - if glbs == lcls: - ns = glbs - else: - ns = glbs.copy() - ns.update(lcls) - - # Add magics to ns so we can show help about them on the Help - # plugin - if with_magics: - line_magics = self.line_magics - cell_magics = self.cell_magics - ns.update(line_magics) - ns.update(cell_magics) - - return ns - def _get_reference_namespace(self, name): """ Return namespace where reference name is defined @@ -356,7 +295,6 @@ def _create_console_kernel_client(self): kernel_manager = KernelManager(kernel_name='spyder_console') kernel_manager.start_kernel() kernel_client = kernel_manager.client() - kernel_client.start_channels() # Register a Pdb instance so that PdbProxy can work kernel_client.execute('import pdb; p=pdb.Pdb(); p.init()', diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index a140afaf..e4adfb64 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -11,13 +11,16 @@ different kernel. """ +import ast + class PdbProxy(object): - remote_pdb_obj = 'get_ipython().kernel._pdb_obj' + remote_pdb_obj = u'get_ipython().kernel._pdb_obj' def __init__(self, kernel_client): self.kernel_client = kernel_client + # --- Custom API def _execute(self, command, interactive=False): """Execute command in a remote Pdb instance.""" if interactive: @@ -25,22 +28,44 @@ def _execute(self, command, interactive=False): else: kc_exec = self.kernel_client.execute - pdb_cmd = self.remote_pdb_obj + '.' + command + pdb_cmd = self.remote_pdb_obj + u'.' + command if interactive: kc_exec(pdb_cmd, store_history=False) else: kc_exec(pdb_cmd, silent=True) + def _get_completions(self, code): + """Get code completions from our Pdb instance.""" + # Ask for completions to the debugger + cmd = (u'__spy_matches__ = ' + self.remote_pdb_obj + u'.' + + u'_get_completions("{}")'.format(code)) + msg_id = self.kernel_client.execute(cmd, + silent=True, + user_expressions={'output':'__spy_matches__'}) + + # Get completions from the reply + reply = self.kernel_client.get_shell_msg(msg_id) + user_expressions = reply['content']['user_expressions'] + try: + str_matches = user_expressions['output']['data']['text/plain'] + try: + return ast.literal_eval(str_matches) + except Exception: + return [] + except KeyError: + return [] + + # --- Pdb API def default(self, line): - self._execute('default("{}")'.format(line), interactive=True) + self._execute(u'default("{}")'.format(line), interactive=True) def postcmd(self, stop, line): - self._execute('postcmd(None, "{}")'.format(line)) + self._execute(u'postcmd(None, "{}")'.format(line)) def do_break(self, arg=None, temporary=0): if arg: - self._execute('do_break("{}", {})'.format(arg, temporary), + self._execute(u'do_break("{}", {})'.format(arg, temporary), interactive=True) else: - self._execute('do_break(None, {})'.format(temporary), + self._execute(u'do_break(None, {})'.format(temporary), interactive=True) diff --git a/spyder_kernels/ipdb/spyderpdb.py b/spyder_kernels/ipdb/spyderpdb.py index c8eda280..5065a3c0 100644 --- a/spyder_kernels/ipdb/spyderpdb.py +++ b/spyder_kernels/ipdb/spyderpdb.py @@ -14,6 +14,7 @@ import sys # Third-party imports +from IPython.core.completer import IPCompleter from IPython.core.debugger import Pdb as ipyPdb from IPython import get_ipython from jupyter_client.manager import KernelManager @@ -28,6 +29,33 @@ pdb.Pdb = ipyPdb +class DummyShell(object): + """Dummy shell to pass to IPCompleter.""" + + @property + def magics_manager(self): + """ + Create a dummy magics manager with the interface + expected by IPCompleter. + """ + class DummyMagicsManager(object): + def lsmagic(self): + return {'line': {}, 'cell': {}} + + return DummyMagicsManager() + + +class PdbCompleter(IPCompleter): + """ + Subclass of IPCompleter without file completions so they don't + interfere with the ones provided by MetaKernel. + """ + + def file_matches(self, text): + """Return and empty list to deactivate file matches.""" + return [] + + class SpyderPdb(pdb.Pdb): """ Pdb custom Spyder class. @@ -36,7 +64,7 @@ class SpyderPdb(pdb.Pdb): send_initial_notification = True starting = True - # --- Methods overriden by us + # --- Public API (overriden by us) def preloop(self): """Ask Spyder for breakpoints before the first prompt is created.""" if self.starting: @@ -49,7 +77,7 @@ def error(self, msg): """ print('***', msg, file=self.stdout) - # --- Methods defined by us + # --- Public API (defined by us) def set_spyder_breakpoints(self, breakpoints): self.clear_all_breaks() #------Really deleting all breakpoints: @@ -124,12 +152,42 @@ def init(self): self.reset() self.setup(sys._getframe().f_back, None) + # Completer + self.completer = PdbCompleter( + shell=DummyShell(), + namespace=self._get_current_namespace() + ) + self.completer.limit_to__all__ = False + def start_ipdb_kernel(self): """Start IPdb kernel.""" self.ipdb_manager = KernelManager() self.ipdb_manager._kernel_spec = IPdbKernelSpec() self.ipdb_manager.start_kernel() + # --- Private API (defined by us) + def _get_completions(self, code): + """Get completions using the current frame namespace.""" + # Update completer namespace before performing the + # completion + self.completer.namespace = self._get_current_namespace() + matches = self.completer.complete(text=None, line_buffer=code)[1] + return matches + + def _get_current_namespace(self): + """Get current namespace.""" + glbs = self.curframe.f_globals + lcls = self.curframe.f_locals + ns = {} + + if glbs == lcls: + ns = glbs + else: + ns = glbs.copy() + ns.update(lcls) + + return ns + @monkeypatch_method(pdb.Pdb, 'Pdb') def __init__(self, completekey='tab', stdin=None, stdout=None, From e412fba2411e55e66c50431668e8861016bb4c50 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 18:08:08 -0500 Subject: [PATCH 09/29] IPdb: Make %down magic work to fix a test --- spyder_kernels/ipdb/kernel.py | 2 +- spyder_kernels/ipdb/pdbproxy.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 3613564e..75481514 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -83,7 +83,7 @@ def __init__(self, *args, **kwargs): self.input_transformer_manager = IPythonInputSplitter( line_input_checker=False) - # For the %matplotlib magic + # For module_completion and do_inspect self.ipyshell = InteractiveShell().instance() self.ipyshell.enable_gui = enable_gui self.mpl_gui = None diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index e4adfb64..e29c37ba 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -69,3 +69,9 @@ def do_break(self, arg=None, temporary=0): else: self._execute(u'do_break(None, {})'.format(temporary), interactive=True) + + def do_down(self, arg): + if arg: + self._execute(u'do_down("{}")'.format(arg), interactive=True) + else: + self._execute(u'do_down(None)', interactive=True) From 74a0818bbdd113916d64cce027df271bd08689e4 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 18:17:04 -0500 Subject: [PATCH 10/29] IPdb: Add error proxy method to fix another test --- spyder_kernels/ipdb/pdbproxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index e29c37ba..6acb9376 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -75,3 +75,6 @@ def do_down(self, arg): self._execute(u'do_down("{}")'.format(arg), interactive=True) else: self._execute(u'do_down(None)', interactive=True) + + def error(self, msg): + self._execute(u'error("{}")'.format(msg), interactive=True) From 2098d24388d5fea29884131ea03513a9d4ffd116 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 2 Oct 2018 18:30:51 -0500 Subject: [PATCH 11/29] IPdb: Remove _get_kernel_ because it makes no sense now --- spyder_kernels/ipdb/kernel.py | 8 -------- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 8 -------- 2 files changed, 16 deletions(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 75481514..d07bfa04 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -88,10 +88,6 @@ def __init__(self, *args, **kwargs): self.ipyshell.enable_gui = enable_gui self.mpl_gui = None - # Add _get_kernel_ - # TODO: Remove this? - builtins._get_kernel_ = self._get_kernel_ - self._remove_unneeded_magics() # --- MetaKernel API @@ -254,10 +250,6 @@ def _show_inline_figures(self): if self.mpl_gui == 'inline': backend_inline.show() - def _get_kernel_(self): - """To add _get_kernel_ function to builtins.""" - return self - def _create_console_kernel_client(self): """Create a kernel client connected to a console kernel.""" try: diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index 75ba161d..e5d2309e 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -297,13 +297,5 @@ def test_shell_partial_quote(ipdb_kernel): """ or volume label syntax is incorrect: '"/home/'""" in text, text -def test_builtins(ipdb_kernel): - kernel = ipdb_kernel - kernel.do_execute('_get_kernel_', None) - text = get_log_text(kernel) - - assert 'IPdbKernel._get_kernel_' in text - - if __name__ == "__main__": pytest.main() From f574a123270fcfe49d9d8a66da150d005ed2efe8 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 13:34:18 -0500 Subject: [PATCH 12/29] IPdb: Make matplotlib integration work again --- spyder_kernels/ipdb/kernel.py | 15 ++-------- .../ipdb/magics/matplotlib_magic.py | 3 +- spyder_kernels/ipdb/pdbproxy.py | 30 +++++++++++++++++-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index d07bfa04..f9e41a6e 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -18,7 +18,6 @@ import os import sys -from ipykernel.eventloops import enable_gui from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.interactiveshell import InteractiveShell from IPython.core.debugger import BdbQuit_excepthook @@ -77,7 +76,8 @@ def __init__(self, *args, **kwargs): # Create Pdb proxy console_kernel_client = self._create_console_kernel_client() - self.debugger = PdbProxy(console_kernel_client) + self.debugger = PdbProxy(parent=self, + kernel_client=console_kernel_client) # To detect if a line is complete self.input_transformer_manager = IPythonInputSplitter( @@ -85,8 +85,6 @@ def __init__(self, *args, **kwargs): # For module_completion and do_inspect self.ipyshell = InteractiveShell().instance() - self.ipyshell.enable_gui = enable_gui - self.mpl_gui = None self._remove_unneeded_magics() @@ -100,10 +98,6 @@ def do_execute_direct(self, code): self.debugger.default(line) self.debugger.postcmd(None, line) - # Post command operations - # TODO: Fix inline figures - # self._show_inline_figures() - def do_is_complete(self, code): """ Given code as string, returns dictionary with 'status' representing @@ -245,11 +239,6 @@ def _phony_stdout(self, text): {'name': 'stdout', 'text': text}) - def _show_inline_figures(self): - """Show Matplotlib inline figures.""" - if self.mpl_gui == 'inline': - backend_inline.show() - def _create_console_kernel_client(self): """Create a kernel client connected to a console kernel.""" try: diff --git a/spyder_kernels/ipdb/magics/matplotlib_magic.py b/spyder_kernels/ipdb/magics/matplotlib_magic.py index efca62e6..23666c0a 100644 --- a/spyder_kernels/ipdb/magics/matplotlib_magic.py +++ b/spyder_kernels/ipdb/magics/matplotlib_magic.py @@ -18,8 +18,7 @@ def line_matplotlib(self, gui): You can set all backends you can with the IPython %matplotlib magic. """ - gui, backend = self.kernel.ipyshell.enable_matplotlib(gui=gui) - self.kernel.mpl_gui = gui + self.kernel.debugger._enable_matplotlib(gui) def register_magics(kernel): diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index 6acb9376..089c6284 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -11,13 +11,17 @@ different kernel. """ +from __future__ import print_function import ast +import sys + class PdbProxy(object): remote_pdb_obj = u'get_ipython().kernel._pdb_obj' - def __init__(self, kernel_client): + def __init__(self, parent, kernel_client): + self.parent = parent self.kernel_client = kernel_client # --- Custom API @@ -30,7 +34,8 @@ def _execute(self, command, interactive=False): pdb_cmd = self.remote_pdb_obj + u'.' + command if interactive: - kc_exec(pdb_cmd, store_history=False) + kc_exec(pdb_cmd, store_history=False, + output_hook=self._output_hook) else: kc_exec(pdb_cmd, silent=True) @@ -55,6 +60,27 @@ def _get_completions(self, code): except KeyError: return [] + def _enable_matplotlib(self, gui): + """Set Matplotlib backend in the remote kernel.""" + self.kernel_client.execute(u"%matplotlib {}".format(gui), + silent=True) + + def _output_hook(self, msg): + """Output hook for execute_interactive.""" + msg_type = msg['header']['msg_type'] + content = msg['content'] + if msg_type == 'stream': + stream = getattr(sys, content['name']) + stream.write(content['text']) + elif msg_type in ('display_data', 'execute_result'): + self.parent.send_response( + self.parent.iopub_socket, + msg_type, + content + ) + elif msg_type == 'error': + print('\n'.join(content['traceback']), file=sys.stderr) + # --- Pdb API def default(self, line): self._execute(u'default("{}")'.format(line), interactive=True) From b030a5f9ebabe5ec670f744c269ae7e6d506f40b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 17:43:22 -0500 Subject: [PATCH 13/29] IPdb: Make %reset magic work again --- spyder_kernels/ipdb/magics/reset_magic.py | 12 ++---------- spyder_kernels/ipdb/pdbproxy.py | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/spyder_kernels/ipdb/magics/reset_magic.py b/spyder_kernels/ipdb/magics/reset_magic.py index 9bd44f88..8048634c 100644 --- a/spyder_kernels/ipdb/magics/reset_magic.py +++ b/spyder_kernels/ipdb/magics/reset_magic.py @@ -12,9 +12,6 @@ # Metakernel imports from metakernel import Magic -# Local imports -from spyder_kernels.utils.test_utils import running_under_pytest - class ResetMagic(Magic): @@ -25,13 +22,8 @@ def line_reset(self, arg=None): Reset the global namespace. """ if self.kernel.debugger: - self.kernel.debugger.reset() - self.kernel.debugger.setup(sys._getframe().f_back, None) + self.kernel.debugger._reset_namespace(arg) def register_magics(kernel): - # This is only useful for our tests - if running_under_pytest(): - kernel.register_magics(ResetMagic) - else: - pass + kernel.register_magics(ResetMagic) diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index 089c6284..ae16cf8a 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -35,9 +35,10 @@ def _execute(self, command, interactive=False): pdb_cmd = self.remote_pdb_obj + u'.' + command if interactive: kc_exec(pdb_cmd, store_history=False, - output_hook=self._output_hook) + output_hook=self._output_hook, + allow_stdin=False) else: - kc_exec(pdb_cmd, silent=True) + kc_exec(pdb_cmd, silent=True, allow_stdin=False) def _get_completions(self, code): """Get code completions from our Pdb instance.""" @@ -81,6 +82,15 @@ def _output_hook(self, msg): elif msg_type == 'error': print('\n'.join(content['traceback']), file=sys.stderr) + def _reset_namespace(self, arg): + if arg: + self.kernel_client.execute_interactive( + u"%reset {}".format(arg), + store_history=False) + else: + print("We can't ask for confirmation in this kernel.\n" + "Please use '%reset -f' to reset your namespace.") + # --- Pdb API def default(self, line): self._execute(u'default("{}")'.format(line), interactive=True) @@ -88,6 +98,10 @@ def default(self, line): def postcmd(self, stop, line): self._execute(u'postcmd(None, "{}")'.format(line)) + def error(self, msg): + self._execute(u'error("{}")'.format(msg), interactive=True) + + # --- Pdb commands def do_break(self, arg=None, temporary=0): if arg: self._execute(u'do_break("{}", {})'.format(arg, temporary), @@ -101,6 +115,3 @@ def do_down(self, arg): self._execute(u'do_down("{}")'.format(arg), interactive=True) else: self._execute(u'do_down(None)', interactive=True) - - def error(self, msg): - self._execute(u'error("{}")'.format(msg), interactive=True) From a5a84d34b137f3daf979fc3a842228783503af1a Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 18:04:39 -0500 Subject: [PATCH 14/29] IPdb: Make %list magic work again --- spyder_kernels/ipdb/magics/down_magic.py | 2 +- spyder_kernels/ipdb/magics/list_magic.py | 2 +- spyder_kernels/ipdb/pdbproxy.py | 15 +++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spyder_kernels/ipdb/magics/down_magic.py b/spyder_kernels/ipdb/magics/down_magic.py index a28b3486..3897841f 100644 --- a/spyder_kernels/ipdb/magics/down_magic.py +++ b/spyder_kernels/ipdb/magics/down_magic.py @@ -16,7 +16,7 @@ def line_down(self, arg=None): Move the current frame count (default one) levels down in the stack trace (to a newer frame). """ - self.kernel.debugger.do_down(arg) + self.kernel.debugger._do_command(u'do_down', arg) line_d = line_down diff --git a/spyder_kernels/ipdb/magics/list_magic.py b/spyder_kernels/ipdb/magics/list_magic.py index 87b6e85e..bb2270af 100644 --- a/spyder_kernels/ipdb/magics/list_magic.py +++ b/spyder_kernels/ipdb/magics/list_magic.py @@ -26,7 +26,7 @@ def line_list(self, arg=None): exception was originally raised or propagated is indicated by ">>", if it differs from the current line. """ - self.kernel.debugger.do_list(arg) + self.kernel.debugger._do_command('do_list', arg) line_l = line_list diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index ae16cf8a..f808a1b0 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -91,6 +91,15 @@ def _reset_namespace(self, arg): print("We can't ask for confirmation in this kernel.\n" "Please use '%reset -f' to reset your namespace.") + def _do_command(self, command, arg): + """ + Method to execute a given Pdb comand with its respective arg. + + Note: This is useful because almost all Pdb commands have a + single arg. + """ + self._execute(u'{}({})'.format(command, arg), interactive=True) + # --- Pdb API def default(self, line): self._execute(u'default("{}")'.format(line), interactive=True) @@ -109,9 +118,3 @@ def do_break(self, arg=None, temporary=0): else: self._execute(u'do_break(None, {})'.format(temporary), interactive=True) - - def do_down(self, arg): - if arg: - self._execute(u'do_down("{}")'.format(arg), interactive=True) - else: - self._execute(u'do_down(None)', interactive=True) From ba412063228b7ca8e0693b20d22a775aee82d7e4 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 18:19:15 -0500 Subject: [PATCH 15/29] Testing: Make tests pass again --- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 2 ++ spyder_kernels/tests/test_kernelmixin.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index e5d2309e..26fddff7 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -284,6 +284,8 @@ def test_sticky_magics(ipdb_kernel): assert 'html removed from session magics' in text +@pytest.mark.skipif(os.environ.get('CI', None) is None, + reason="It's not meant to be run outside of CIs") def test_shell_partial_quote(ipdb_kernel): kernel = ipdb_kernel if os.name != 'nt': diff --git a/spyder_kernels/tests/test_kernelmixin.py b/spyder_kernels/tests/test_kernelmixin.py index 1fa9ad57..03a52815 100644 --- a/spyder_kernels/tests/test_kernelmixin.py +++ b/spyder_kernels/tests/test_kernelmixin.py @@ -37,7 +37,9 @@ # Fixtures # ============================================================================= @pytest.fixture(scope="module", - params=[IPdbKernel, + params=[# TODO: Determine what we need to move back + # again to ConsoleKernel before restoring + # IPdbKernel here. ConsoleKernel]) def kernel(request): """Kernel fixture""" From 7cd5ad315f43240ffe6fa8eb49c151ec6ed3f03b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 18:27:44 -0500 Subject: [PATCH 16/29] Testing: Increase timeout when waiting for signals to be emitted --- spyder_kernels/ipdb/tests/test_matplotlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/ipdb/tests/test_matplotlib.py b/spyder_kernels/ipdb/tests/test_matplotlib.py index 40f731e6..e9dae76d 100644 --- a/spyder_kernels/ipdb/tests/test_matplotlib.py +++ b/spyder_kernels/ipdb/tests/test_matplotlib.py @@ -40,7 +40,7 @@ def test_matplotlib_inline(qtconsole, qtbot): shell.execute("%matplotlib inline") # Make a plot - with qtbot.waitSignal(shell.executed): + with qtbot.waitSignal(shell.executed, timeout=5000): shell.execute("import matplotlib.pyplot as plt; plt.plot(range(10))") # Assert that there's a plot in the console @@ -61,7 +61,7 @@ def test_matplotlib_qt(qtconsole, qtbot): shell.execute("%matplotlib qt") # Make a plot - with qtbot.waitSignal(shell.executed): + with qtbot.waitSignal(shell.executed, timeout=5000): shell.execute("import matplotlib.pyplot as plt; plt.plot(range(10))") # Assert we have three prompts in the console, meaning that the From bcc6f8a43f57ee73f772e203d93fc4e840045f78 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Oct 2018 18:40:42 -0500 Subject: [PATCH 17/29] IPdb: Remove code that is not needed anymore --- spyder_kernels/ipdb/backend_inline.py | 55 --------------------------- spyder_kernels/ipdb/kernel.py | 47 ----------------------- 2 files changed, 102 deletions(-) delete mode 100644 spyder_kernels/ipdb/backend_inline.py diff --git a/spyder_kernels/ipdb/backend_inline.py b/spyder_kernels/ipdb/backend_inline.py deleted file mode 100644 index 3b8670d1..00000000 --- a/spyder_kernels/ipdb/backend_inline.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2018- Spyder Kernels Contributors -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. -# ----------------------------------------------------------------------------- - -""" -Functions for our inline backend. - -This is a simplified version of some functions present in -ipykernel/pylab/backend_inline.py -""" - -from ipykernel.pylab.backend_inline import _fetch_figure_metadata -from IPython.display import Image -from metakernel.display import display - -from spyder_kernels.py3compat import io, PY2 - - -def get_image(figure): - """ - Get image display object from a Matplotlib figure. - - The idea to get png/svg from a figure was taken from - https://stackoverflow.com/a/12145161/438386 - """ - # Print figure to a bytes stream - if PY2: - data = io.StringIO() - else: - data = io.BytesIO() - figure.canvas.print_figure(data, bbox_inches='tight') - - # Get figure metadata - metadata = _fetch_figure_metadata(figure) - - img = Image(data=data.getvalue(), metadata=metadata) - return img - - -def show(): - """ - Show all figures as PNG payloads sent to the Jupyter clients. - """ - import matplotlib - from matplotlib._pylab_helpers import Gcf - - try: - for figure_manager in Gcf.get_all_fig_managers(): - display(get_image(figure_manager.canvas.figure)) - finally: - if Gcf.get_all_fig_managers(): - matplotlib.pyplot.close('all') diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index f9e41a6e..ea0778ba 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -20,34 +20,17 @@ from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.interactiveshell import InteractiveShell -from IPython.core.debugger import BdbQuit_excepthook from IPython.utils.tokenutil import token_at_cursor from jupyter_client.blocking.client import BlockingKernelClient from metakernel import MetaKernel from spyder_kernels._version import __version__ -from spyder_kernels.ipdb import backend_inline from spyder_kernels.ipdb.pdbproxy import PdbProxy from spyder_kernels.kernelmixin import BaseKernelMixIn from spyder_kernels.py3compat import builtins from spyder_kernels.utils.module_completion import module_completion -class PhonyStdout(object): - - def __init__(self, write_func): - self._write_func = write_func - - def flush(self): - pass - - def write(self, s): - self._write_func(s) - - def close(self): - pass - - class IPdbKernel(BaseKernelMixIn, MetaKernel): implementation = "IPdb Kernel" implementation_version = __version__ @@ -209,36 +192,6 @@ def _remove_unneeded_magics(self): except: pass - def _get_reference_namespace(self, name): - """ - Return namespace where reference name is defined - - It returns the globals() if reference has not yet been defined - """ - glbs = self._mglobals() - if self.debugger.curframe is None: - return glbs - else: - lcls = self.debugger.curframe.f_locals - if name in lcls: - return lcls - else: - return glbs - - def _mglobals(self): - """Return current globals""" - if self.debugger.curframe is not None: - return self.debugger.curframe.f_globals - else: - return {} - - def _phony_stdout(self, text): - self.log.debug(text) - self.send_response(self.iopub_socket, - 'stream', - {'name': 'stdout', - 'text': text}) - def _create_console_kernel_client(self): """Create a kernel client connected to a console kernel.""" try: From 98616494c87fb701c5057276244ffaea1fc00883 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 27 Dec 2018 10:47:42 -0500 Subject: [PATCH 18/29] IPdb: Fix code completions with IPython 7.2+ --- spyder_kernels/ipdb/spyderpdb.py | 5 ++++- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/ipdb/spyderpdb.py b/spyder_kernels/ipdb/spyderpdb.py index 5065a3c0..ce7bca8b 100644 --- a/spyder_kernels/ipdb/spyderpdb.py +++ b/spyder_kernels/ipdb/spyderpdb.py @@ -157,7 +157,10 @@ def init(self): shell=DummyShell(), namespace=self._get_current_namespace() ) - self.completer.limit_to__all__ = False + + # If Jedi is activated completions stop to work! + if not PY2: + self.completer.use_jedi = False def start_ipdb_kernel(self): """Start IPdb kernel.""" diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index 08c578d3..26fddff7 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -138,7 +138,6 @@ def test_help(ipdb_kernel): assert resp == None -@pytest.mark.xfail def test_complete(ipdb_kernel): """Check completion.""" kernel = ipdb_kernel From c3ed33d361cfebc505c03eeb3b1d1cae8fdc0e67 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 27 Dec 2018 11:14:40 -0500 Subject: [PATCH 19/29] Testing: Skip a test on Windows because it's failing --- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index 26fddff7..2f95a65b 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -90,6 +90,8 @@ def test_shell_magic(ipdb_kernel): os.remove('TEST.txt') +@pytest.mark.skipif(os.name == 'nt', + reason="It's failing on Windows") def test_break_magic(ipdb_kernel): """Test %break magic.""" kernel = ipdb_kernel From febfe0d8b5e7184b6318589d0b08f58b623bdf67 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 27 Dec 2018 18:22:27 -0500 Subject: [PATCH 20/29] Testing: Remove teardown part of ipdb_kernel fixture because it's doing nothing --- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index 2f95a65b..e789c06f 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -36,16 +36,10 @@ # Fixtures # ============================================================================= @pytest.fixture -def ipdb_kernel(request): +def ipdb_kernel(): """IPdb kernel fixture""" # Get kernel instance kernel = get_kernel(kernel_class=IPdbKernel) - - # Teardown - def reset_kernel(): - kernel.do_execute('%reset', True) - - request.addfinalizer(reset_kernel) return kernel From c62dd3769ed6229f0bad1d580375158d64d8c8c6 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 27 Dec 2018 23:21:04 -0500 Subject: [PATCH 21/29] Testing: Increase codecov threshold --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index a3cd16ce..d72de714 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,7 +9,7 @@ coverage: status: project: default: - threshold: 1% + threshold: 5% patch: no changes: no From 2bf2fbcbca87e8afc1f0a21fe6bf5b56966e1041 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 27 Dec 2018 23:42:56 -0500 Subject: [PATCH 22/29] Testing: Skip all ipdb test on Linux and Windows --- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index e789c06f..5cfb013b 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -26,6 +26,14 @@ from spyder_kernels.ipdb.kernel import IPdbKernel from spyder_kernels.py3compat import PY2 + +# ============================================================================= +# Skip on Linux/Windows and our CIs because these tests time out too frequently +# ============================================================================= +if os.environ.get('CI', None) is not None and not sys.platform == 'darwin': + pytestmark = pytest.mark.skip + + # ============================================================================= # Constants # ============================================================================= From 16e495fa16ecca18bd76af72d0cad1e8af968ac3 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 30 Dec 2018 22:20:16 -0500 Subject: [PATCH 23/29] IPdb: Refactor code to get a response from the remote Pdb instance This will allow us to get the response of other methods in the instance --- spyder_kernels/ipdb/pdbproxy.py | 57 ++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index f808a1b0..32ab0528 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -15,6 +15,8 @@ import ast import sys +from spyder_kernels.py3compat import to_text_string + class PdbProxy(object): @@ -40,26 +42,59 @@ def _execute(self, command, interactive=False): else: kc_exec(pdb_cmd, silent=True, allow_stdin=False) - def _get_completions(self, code): - """Get code completions from our Pdb instance.""" - # Ask for completions to the debugger - cmd = (u'__spy_matches__ = ' + self.remote_pdb_obj + u'.' + - u'_get_completions("{}")'.format(code)) + def _silent_exec_method(self, method, args=None): + """ + Silently execute a method of our remote Pdb instance and get its + response. + + Parameters + ---------- + method: string + Method name + args: string + Args to be passed to the method (optional) + """ + method = to_text_string(method) + + # Pass args to the method call, if any + if args is not None: + args = to_text_string(args) + method_call = method + u'("{}")'.format(args) + else: + method_call = method + u'()' + + # Ask the remote instance to execute the method call + cmd = (u'__dbg_response__ = ' + self.remote_pdb_obj + u'.' + + method_call) msg_id = self.kernel_client.execute(cmd, silent=True, - user_expressions={'output':'__spy_matches__'}) + user_expressions={'output':'__dbg_response__'}) - # Get completions from the reply + # Get response reply = self.kernel_client.get_shell_msg(msg_id) user_expressions = reply['content']['user_expressions'] try: - str_matches = user_expressions['output']['data']['text/plain'] + return user_expressions['output']['data']['text/plain'] + except KeyError: + return None + + def _get_completions(self, code): + """ + Get code completions from our Pdb instance. + + Parameters + ---------- + code: string + Code to get completions for. + """ + response = self._silent_exec_method('_get_completions', code) + if response is None: + return [] + else: try: - return ast.literal_eval(str_matches) + return ast.literal_eval(response) except Exception: return [] - except KeyError: - return [] def _enable_matplotlib(self, gui): """Set Matplotlib backend in the remote kernel.""" From 642627d956f8ae4468e56bf23364a56148623d5d Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 1 Jan 2019 22:42:02 -0500 Subject: [PATCH 24/29] IPdb: Ask for breakpoints to Spyder at startup --- spyder_kernels/console/kernel.py | 8 +++++++- spyder_kernels/ipdb/kernel.py | 18 +++++++++++++++++- spyder_kernels/ipdb/pdbproxy.py | 13 +++++++++++++ spyder_kernels/ipdb/spyderpdb.py | 12 +++++++----- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index dadfc15a..ea69c501 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -33,6 +33,9 @@ def __init__(self, *args, **kwargs): self._do_publish_pdb_state = True self._mpl_backend_error = None + # To test this kernel with the IPdb one + self.testing_ipdb = os.environ.get('SPY_TEST_IPDB_KERNEL') is not None + @property def _pdb_frame(self): """Return current Pdb frame if there is any""" @@ -103,7 +106,10 @@ def _set_spyder_breakpoints(self, breakpoints): def _ask_spyder_for_breakpoints(self): if self._pdb_obj: - self.send_spyder_msg('set_breakpoints') + if not self.testing_ipdb: + self.send_spyder_msg('set_breakpoints') + else: + self._pdb_obj.starting = False # --- For Matplotlib def _set_mpl_backend(self, backend, pylab=False): diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index ea0778ba..34eae15c 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -17,6 +17,7 @@ import functools import os import sys +import time from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.interactiveshell import InteractiveShell @@ -69,6 +70,21 @@ def __init__(self, *args, **kwargs): # For module_completion and do_inspect self.ipyshell = InteractiveShell().instance() + # Wait for ~3 sec to see if the kernel is ready + is_ready = False + for _ in range(15): + if self.debugger._is_ready(): + is_ready = True + break + else: + time.sleep(0.2) + continue + + if not is_ready: + # TODO: Replace this by a message printed in the console + # kernel + raise RuntimeError('Kernel is not ready') + self._remove_unneeded_magics() # --- MetaKernel API @@ -227,7 +243,7 @@ def _create_console_kernel_client(self): # *Note*: This is useful for testing purposes only! from jupyter_client.manager import KernelManager kernel_manager = KernelManager(kernel_name='spyder_console') - kernel_manager.start_kernel() + kernel_manager.start_kernel(env={'SPY_TEST_IPDB_KERNEL': 'True'}) kernel_client = kernel_manager.client() # Register a Pdb instance so that PdbProxy can work diff --git a/spyder_kernels/ipdb/pdbproxy.py b/spyder_kernels/ipdb/pdbproxy.py index 32ab0528..6b5dd67d 100644 --- a/spyder_kernels/ipdb/pdbproxy.py +++ b/spyder_kernels/ipdb/pdbproxy.py @@ -96,6 +96,19 @@ def _get_completions(self, code): except Exception: return [] + def _is_ready(self): + """ + Check if the remote Pdb instance is ready to start debugging. + """ + response = self._silent_exec_method('_is_ready') + if response is None: + return False + else: + try: + return ast.literal_eval(response) + except Exception: + return False + def _enable_matplotlib(self, gui): """Set Matplotlib backend in the remote kernel.""" self.kernel_client.execute(u"%matplotlib {}".format(gui), diff --git a/spyder_kernels/ipdb/spyderpdb.py b/spyder_kernels/ipdb/spyderpdb.py index ce7bca8b..153f0721 100644 --- a/spyder_kernels/ipdb/spyderpdb.py +++ b/spyder_kernels/ipdb/spyderpdb.py @@ -65,11 +65,6 @@ class SpyderPdb(pdb.Pdb): starting = True # --- Public API (overriden by us) - def preloop(self): - """Ask Spyder for breakpoints before the first prompt is created.""" - if self.starting: - get_ipython().kernel._ask_spyder_for_breakpoints() - def error(self, msg): """ Error message (method defined for compatibility reasons with Python 2, @@ -162,6 +157,9 @@ def init(self): if not PY2: self.completer.use_jedi = False + # Ask Spyder to send us its saved breakpoints + get_ipython().kernel._ask_spyder_for_breakpoints() + def start_ipdb_kernel(self): """Start IPdb kernel.""" self.ipdb_manager = KernelManager() @@ -191,6 +189,10 @@ def _get_current_namespace(self): return ns + def _is_ready(self): + """Check if the Pdb instance is ready to start debugging.""" + return not self.starting + @monkeypatch_method(pdb.Pdb, 'Pdb') def __init__(self, completekey='tab', stdin=None, stdout=None, From 57d85981dd2c7d963e42aef6fcbb5e3b3c499f33 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 1 Jan 2019 23:08:12 -0500 Subject: [PATCH 25/29] IPdb: Fix Matplotlib tests --- spyder_kernels/ipdb/kernel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 34eae15c..2a707d26 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -242,8 +242,14 @@ def _create_console_kernel_client(self): # kernel can stand on its own. # *Note*: This is useful for testing purposes only! from jupyter_client.manager import KernelManager + + # Set environment + env = os.environ.copy() + env['SPY_TEST_IPDB_KERNEL'] = 'True' + + # Create kernel kernel_manager = KernelManager(kernel_name='spyder_console') - kernel_manager.start_kernel(env={'SPY_TEST_IPDB_KERNEL': 'True'}) + kernel_manager.start_kernel(env=env) kernel_client = kernel_manager.client() # Register a Pdb instance so that PdbProxy can work From d04bc483db4e53ebfd622356619379139fc86716 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 2 Jan 2019 11:55:11 -0500 Subject: [PATCH 26/29] IPdb: Add a testing kwarg to it to better control how we test it --- spyder_kernels/console/kernel.py | 2 +- .../console/tests/test_console_kernel.py | 3 +- spyder_kernels/ipdb/kernel.py | 132 ++++++++++-------- spyder_kernels/ipdb/tests/test_ipdb_kernel.py | 4 +- spyder_kernels/utils/test_utils.py | 6 +- 5 files changed, 84 insertions(+), 63 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index ea69c501..ff4e7b28 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -25,7 +25,7 @@ class ConsoleKernel(BaseKernelMixIn, IPythonKernel): """Spyder kernel for Jupyter""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, testing=False, **kwargs): super(ConsoleKernel, self).__init__(*args, **kwargs) self._pdb_obj = None diff --git a/spyder_kernels/console/tests/test_console_kernel.py b/spyder_kernels/console/tests/test_console_kernel.py index 66e9074e..f28f88bd 100644 --- a/spyder_kernels/console/tests/test_console_kernel.py +++ b/spyder_kernels/console/tests/test_console_kernel.py @@ -19,6 +19,7 @@ import pytest # Local imports +from spyder_kernels.console.kernel import ConsoleKernel from spyder_kernels.utils.test_utils import get_kernel from spyder_kernels.py3compat import PY3, to_text_string @@ -37,7 +38,7 @@ def console_kernel(request): """Console kernel fixture""" # Get kernel instance - kernel = get_kernel() + kernel = get_kernel(kernel_class=ConsoleKernel) kernel.namespace_view_settings = {'check_all': False, 'exclude_private': True, 'exclude_uppercase': True, diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 2a707d26..547a4ab8 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -46,22 +46,33 @@ class IPdbKernel(BaseKernelMixIn, MetaKernel): "help_links": MetaKernel.help_links, } + # IMPORTANT: The kernelspec generated by Metakernel is only meant + # for testing! kernel_json = { "argv": [sys.executable, "-m", "spyder_kernels.ipdb", "-f", "{connection_file}"], + "env": {'SPY_TEST_IPDB_KERNEL': 'True'}, "display_name": "IPdb", "language": "ipython", "mimetype": "text/x-python", "name": "ipdb_kernel", } - def __init__(self, *args, **kwargs): + def __init__(self, *args, testing=False, **kwargs): super(IPdbKernel, self).__init__(*args, **kwargs) + self.testing = testing + + if os.environ.get('SPY_TEST_IPDB_KERNEL') is not None: + self.testing = True + + # Create a kernel client connected to the console kernel + if not self.testing: + console_client = self._create_console_client() + else: + console_client = self._create_dummy_client() # Create Pdb proxy - console_kernel_client = self._create_console_kernel_client() - self.debugger = PdbProxy(parent=self, - kernel_client=console_kernel_client) + self.debugger = PdbProxy(parent=self, kernel_client=console_client) # To detect if a line is complete self.input_transformer_manager = IPythonInputSplitter( @@ -70,6 +81,9 @@ def __init__(self, *args, **kwargs): # For module_completion and do_inspect self.ipyshell = InteractiveShell().instance() + # Remove unneeded magics that come by default with Metakernel + self._remove_unneeded_magics() + # Wait for ~3 sec to see if the kernel is ready is_ready = False for _ in range(15): @@ -81,11 +95,12 @@ def __init__(self, *args, **kwargs): continue if not is_ready: - # TODO: Replace this by a message printed in the console - # kernel - raise RuntimeError('Kernel is not ready') - - self._remove_unneeded_magics() + if not self.testing: + # TODO: Add here a message printed in the console + # kernel saying that IPdb kernel failed to start + pass + else: + raise RuntimeError('Kernel is not ready') # --- MetaKernel API def do_execute_direct(self, code): @@ -208,52 +223,59 @@ def _remove_unneeded_magics(self): except: pass - def _create_console_kernel_client(self): + def _create_console_client(self): """Create a kernel client connected to a console kernel.""" - try: - # Retrieve connection info from the environment - shell_port = int(os.environ['SPY_CONSOLE_SHELL_PORT']) - iopub_port = int(os.environ['SPY_CONSOLE_IOPUB_PORT']) - stdin_port = int(os.environ['SPY_CONSOLE_STDIN_PORT']) - control_port = int(os.environ['SPY_CONSOLE_CONTROL_PORT']) - hb_port = int(os.environ['SPY_CONSOLE_HB_PORT']) - ip = os.environ['SPY_CONSOLE_IP'] - key = os.environ['SPY_CONSOLE_KEY'] - transport = os.environ['SPY_CONSOLE_TRANSPORT'] - signature_scheme = os.environ['SPY_CONSOLE_SIGNATURE_SCHEME'] - - # Create info dict - info = dict(shell_port=shell_port, - iopub_port=iopub_port, - stdin_port=stdin_port, - control_port=control_port, - hb_port=hb_port, - ip=ip, - key=key, - transport=transport, - signature_scheme=signature_scheme) - - # Create kernel client - kernel_client = BlockingKernelClient() - kernel_client.load_connection_info(info) - kernel_client.start_channels() - except KeyError: - # Create a console kernel to interact with, so this - # kernel can stand on its own. - # *Note*: This is useful for testing purposes only! - from jupyter_client.manager import KernelManager - - # Set environment - env = os.environ.copy() - env['SPY_TEST_IPDB_KERNEL'] = 'True' - - # Create kernel - kernel_manager = KernelManager(kernel_name='spyder_console') - kernel_manager.start_kernel(env=env) - kernel_client = kernel_manager.client() - - # Register a Pdb instance so that PdbProxy can work - kernel_client.execute('import pdb; p=pdb.Pdb(); p.init()', - silent=True) + # Retrieve connection info from the environment + shell_port = int(os.environ['SPY_CONSOLE_SHELL_PORT']) + iopub_port = int(os.environ['SPY_CONSOLE_IOPUB_PORT']) + stdin_port = int(os.environ['SPY_CONSOLE_STDIN_PORT']) + control_port = int(os.environ['SPY_CONSOLE_CONTROL_PORT']) + hb_port = int(os.environ['SPY_CONSOLE_HB_PORT']) + ip = os.environ['SPY_CONSOLE_IP'] + key = os.environ['SPY_CONSOLE_KEY'] + transport = os.environ['SPY_CONSOLE_TRANSPORT'] + signature_scheme = os.environ['SPY_CONSOLE_SIGNATURE_SCHEME'] + + # Create info dict + info = dict(shell_port=shell_port, + iopub_port=iopub_port, + stdin_port=stdin_port, + control_port=control_port, + hb_port=hb_port, + ip=ip, + key=key, + transport=transport, + signature_scheme=signature_scheme) + + # Create kernel client + kernel_client = BlockingKernelClient() + kernel_client.load_connection_info(info) + kernel_client.start_channels() + + return kernel_client + + def _create_dummy_client(self): + """ + Create a dummy console kernel client. + + This is only needed for tests. + """ + # Create a console kernel to interact with, so this + # kernel can stand on its own. + # *Note*: This is useful for testing purposes only! + from jupyter_client.manager import KernelManager + + # We need this for tests to pass! + env = os.environ.copy() + env['SPY_TEST_IPDB_KERNEL'] = 'True' + + # Create kernel + kernel_manager = KernelManager(kernel_name='spyder_console') + kernel_manager.start_kernel(env=env) + kernel_client = kernel_manager.client() + + # Register a Pdb instance so that PdbProxy can work + kernel_client.execute('import pdb; p=pdb.Pdb(); p.init()', + silent=True) return kernel_client diff --git a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py index 5cfb013b..5d3ec7cc 100644 --- a/spyder_kernels/ipdb/tests/test_ipdb_kernel.py +++ b/spyder_kernels/ipdb/tests/test_ipdb_kernel.py @@ -19,12 +19,12 @@ import sys # Test library imports -from metakernel.tests.utils import get_kernel, get_log_text import pytest # Local imports from spyder_kernels.ipdb.kernel import IPdbKernel from spyder_kernels.py3compat import PY2 +from spyder_kernels.utils.test_utils import get_kernel, get_log_text # ============================================================================= @@ -47,7 +47,7 @@ def ipdb_kernel(): """IPdb kernel fixture""" # Get kernel instance - kernel = get_kernel(kernel_class=IPdbKernel) + kernel = get_kernel(kernel_class=IPdbKernel, testing=True) return kernel diff --git a/spyder_kernels/utils/test_utils.py b/spyder_kernels/utils/test_utils.py index 3e44b5aa..5812a064 100644 --- a/spyder_kernels/utils/test_utils.py +++ b/spyder_kernels/utils/test_utils.py @@ -18,10 +18,8 @@ except ImportError: from io import StringIO -from spyder_kernels.console.kernel import ConsoleKernel - -def get_kernel(kernel_class=ConsoleKernel): +def get_kernel(kernel_class, testing=False): """Get an instance of a kernel with the kernel class given.""" log = logging.getLogger('test') log.setLevel(logging.DEBUG) @@ -38,7 +36,7 @@ def get_kernel(kernel_class=ConsoleKernel): kernel = kernel_class(session=ss.Session(), iopub_socket=iopub_socket, - log=log) + log=log, testing=testing) return kernel From 861ada3f3f7799c2b05970f59cf0db26ddc1f7c7 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 2 Jan 2019 12:06:11 -0500 Subject: [PATCH 27/29] Fix syntax error in Python 2 --- spyder_kernels/console/kernel.py | 2 +- spyder_kernels/ipdb/kernel.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index ff4e7b28..39b5f14a 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -25,7 +25,7 @@ class ConsoleKernel(BaseKernelMixIn, IPythonKernel): """Spyder kernel for Jupyter""" - def __init__(self, *args, testing=False, **kwargs): + def __init__(self, testing=False, *args, **kwargs): super(ConsoleKernel, self).__init__(*args, **kwargs) self._pdb_obj = None diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 547a4ab8..10f46e7d 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -58,7 +58,7 @@ class IPdbKernel(BaseKernelMixIn, MetaKernel): "name": "ipdb_kernel", } - def __init__(self, *args, testing=False, **kwargs): + def __init__(self, testing=False, *args, **kwargs): super(IPdbKernel, self).__init__(*args, **kwargs) self.testing = testing From bb32eae8775f1f85965d362e22bafa03769e78b1 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 2 Jan 2019 12:49:16 -0500 Subject: [PATCH 28/29] IPdb: Skip Matplotlib tests on Windows and Python 2 --- spyder_kernels/ipdb/tests/test_matplotlib.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spyder_kernels/ipdb/tests/test_matplotlib.py b/spyder_kernels/ipdb/tests/test_matplotlib.py index 3b9222c0..e6d3868b 100644 --- a/spyder_kernels/ipdb/tests/test_matplotlib.py +++ b/spyder_kernels/ipdb/tests/test_matplotlib.py @@ -6,10 +6,14 @@ # (see spyder_kernels/__init__.py for details) # ----------------------------------------------------------------------------- +import os + from flaky import flaky from qtconsole.qtconsoleapp import JupyterQtConsoleApp import pytest +from spyder_kernels.py3compat import PY2 + SHELL_TIMEOUT = 20000 @@ -28,6 +32,8 @@ def qtconsole(qtbot): @flaky(max_runs=3) +@pytest.mark.skipif(os.name == 'nt' and PY2, + reason='Fails on Windows and Python 2') def test_matplotlib_inline(qtconsole, qtbot): """Test that %matplotlib inline is working.""" window = qtconsole.window @@ -50,6 +56,8 @@ def test_matplotlib_inline(qtconsole, qtbot): @flaky(max_runs=3) +@pytest.mark.skipif(os.name == 'nt' and PY2, + reason='Fails on Windows and Python 2') def test_matplotlib_qt(qtconsole, qtbot): """Test that %matplotlib qt is working.""" window = qtconsole.window From 9d62e2e891fb133b086ba42d6f8a3e0c21277b45 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 2 Jan 2019 12:57:06 -0500 Subject: [PATCH 29/29] IPdb: Stop inheriting from BaseKernelMixIn --- spyder_kernels/ipdb/kernel.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spyder_kernels/ipdb/kernel.py b/spyder_kernels/ipdb/kernel.py index 10f46e7d..81373e5c 100644 --- a/spyder_kernels/ipdb/kernel.py +++ b/spyder_kernels/ipdb/kernel.py @@ -27,12 +27,10 @@ from spyder_kernels._version import __version__ from spyder_kernels.ipdb.pdbproxy import PdbProxy -from spyder_kernels.kernelmixin import BaseKernelMixIn -from spyder_kernels.py3compat import builtins from spyder_kernels.utils.module_completion import module_completion -class IPdbKernel(BaseKernelMixIn, MetaKernel): +class IPdbKernel(MetaKernel): implementation = "IPdb Kernel" implementation_version = __version__ language = "ipdb"