From 4e6c8cb75e0a1ab1a3d1d04970a71a424d25ba0c Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 22 Aug 2023 10:18:56 -0500 Subject: [PATCH 01/27] Update Changelog --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de4cfdd0..0e6cb105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # History of changes +## Version 3.0.0b2 (2023-08-22) + +### Pull Requests Merged + +* [PR 465](https://github.com/spyder-ide/spyder-kernels/pull/465) - Save temporary file in test to temporary location, by [@juliangilbey](https://github.com/juliangilbey) +* [PR 464](https://github.com/spyder-ide/spyder-kernels/pull/464) - Remove locals inspection, by [@impact27](https://github.com/impact27) +* [PR 460](https://github.com/spyder-ide/spyder-kernels/pull/460) - PR: Add a global filter flag to settings, by [@jsbautista](https://github.com/jsbautista) +* [PR 445](https://github.com/spyder-ide/spyder-kernels/pull/445) - PR: Add `exitdb` command and some speed optimizations to the debugger, by [@impact27](https://github.com/impact27) +* [PR 429](https://github.com/spyder-ide/spyder-kernels/pull/429) - PR: Add a comm handler decorator, by [@impact27](https://github.com/impact27) +* [PR 411](https://github.com/spyder-ide/spyder-kernels/pull/411) - PR: Remove `set_debug_state` and `do_where` calls, by [@impact27](https://github.com/impact27) + +In this release 6 pull requests were closed. + + +---- + + ## Version 3.0.0b1 (2023-06-14) ### Issues Closed From 1b33f62c6ec2c404f9171042a9e62a3a4172a4f2 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 22 Aug 2023 10:20:25 -0500 Subject: [PATCH 02/27] Release 3.0.0b2 --- spyder_kernels/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/_version.py b/spyder_kernels/_version.py index 788cf5f4..ccc43783 100644 --- a/spyder_kernels/_version.py +++ b/spyder_kernels/_version.py @@ -8,5 +8,5 @@ """Version File.""" -VERSION_INFO = (3, 0, 0, 'dev0') +VERSION_INFO = (3, 0, '0b2') __version__ = '.'.join(map(str, VERSION_INFO)) From efbade5ff00d7fb609d6cffa572bdef4fd096069 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 22 Aug 2023 10:24:01 -0500 Subject: [PATCH 03/27] Back to work --- spyder_kernels/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/_version.py b/spyder_kernels/_version.py index ccc43783..788cf5f4 100644 --- a/spyder_kernels/_version.py +++ b/spyder_kernels/_version.py @@ -8,5 +8,5 @@ """Version File.""" -VERSION_INFO = (3, 0, '0b2') +VERSION_INFO = (3, 0, 0, 'dev0') __version__ = '.'.join(map(str, VERSION_INFO)) From 7eb7ec3f4a432a5f86e2aa36fd2dbd2227c39ebc Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 9 Sep 2023 19:02:08 +0200 Subject: [PATCH 04/27] kernel configuration --- spyder_kernels/console/kernel.py | 43 +++++++++++++++++++++----------- spyder_kernels/console/shell.py | 2 -- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index d900d8a7..b9bb5dc8 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -114,7 +114,6 @@ def publish_state(self): except Exception: pass - @comm_handler def enable_faulthandler(self): """ Open a file to save the faulthandling and identifiers for @@ -260,11 +259,6 @@ def get_current_frames(self, ignore_internal_threads=True): return frames # --- For the Variable Explorer - @comm_handler - def set_namespace_view_settings(self, settings): - """Set namespace_view_settings.""" - self.namespace_view_settings = settings - @comm_handler def get_namespace_view(self, frame=None): """ @@ -599,11 +593,34 @@ def set_autocall(self, autocall): # --- Additional methods @comm_handler - def set_cwd(self, dirname): - """Set current working directory.""" - self._cwd_initialised = True - os.chdir(dirname) - self.publish_state() + def set_configuration(self, dic): + """Set kernel configuration""" + ret = {} + for key, value in dic.items(): + if key == "cwd": + self._cwd_initialised = True + os.chdir(value) + self.publish_state() + elif key == "namespace_view_settings": + self.namespace_view_settings = value + self.publish_state() + elif key == "pdb": + self.shell.set_pdb_configuration(value) + elif key == "faulthandler": + ret[key] = self.enable_faulthandler() + elif key == "show_mpl_backend_errors": + self.show_mpl_backend_errors() + elif key == "check_special_kernel": + ret[key] = self.check_special_kernel() + elif key == "color scheme": + if value == "dark": + # Needed to change the colors of tracebacks + self.shell.run_line_magic("colors", "linux") + self.set_sympy_forecolor(background_color='dark') + elif value == "light": + self.shell.run_line_magic("colors", "lightbg") + self.set_sympy_forecolor(background_color='light') + return ret def get_cwd(self): """Get current working directory.""" @@ -631,8 +648,7 @@ def close_all_mpl_figures(self): except: pass - @comm_handler - def is_special_kernel_valid(self): + def check_special_kernel(self): """ Check if optional dependencies are available for special consoles. """ @@ -876,7 +892,6 @@ def _set_mpl_inline_rc_config(self, option, value): # Needed in case matplolib isn't installed pass - @comm_handler def show_mpl_backend_errors(self): """Show Matplotlib backend errors after the prompt is ready.""" if self._mpl_backend_error is not None: diff --git a/spyder_kernels/console/shell.py b/spyder_kernels/console/shell.py index 49cde462..098a9754 100644 --- a/spyder_kernels/console/shell.py +++ b/spyder_kernels/console/shell.py @@ -27,7 +27,6 @@ from spyder_kernels.customize.namespace_manager import NamespaceManager from spyder_kernels.customize.spyderpdb import SpyderPdb from spyder_kernels.customize.code_runner import SpyderCodeRunner -from spyder_kernels.comms.frontendcomm import CommError from spyder_kernels.comms.decorators import comm_handler from spyder_kernels.utils.mpl import automatic_backend @@ -101,7 +100,6 @@ def enable_matplotlib(self, gui=None): return gui, backend # --- For Pdb namespace integration - @comm_handler def set_pdb_configuration(self, pdb_conf): """ Set Pdb configuration. From 937e7b75b4576e0d7b4b0300577fae57c75afb32 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 9 Sep 2023 20:42:19 +0200 Subject: [PATCH 05/27] fix bug --- spyder_kernels/customize/spyderpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/customize/spyderpdb.py b/spyder_kernels/customize/spyderpdb.py index b9d76f63..0d26986d 100755 --- a/spyder_kernels/customize/spyderpdb.py +++ b/spyder_kernels/customize/spyderpdb.py @@ -861,7 +861,7 @@ def get_pdb_state(self): hidden = self.hidden_frames(self.stack) pdb_stack = [f for f, h in zip(pdb_stack, hidden) if not h] # Adjust the index - pdb_index -= sum(hidden[:pdb_index]) + pdb_index -= sum([bool(i) for i in hidden[:pdb_index]]) state['stack'] = (pdb_stack, pdb_index) From feff51d09c16f5588ea9f03478185e57898ba2ca Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 10 Sep 2023 13:55:12 +0200 Subject: [PATCH 06/27] update PR --- spyder_kernels/console/kernel.py | 29 +++++++++++++++++++++++++++ spyder_kernels/console/shell.py | 7 ------- spyder_kernels/customize/spyderpdb.py | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index b9bb5dc8..a26e4c44 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -29,6 +29,7 @@ from zmq.utils.garbage import gc # Local imports +import spyder_kernels from spyder_kernels.comms.frontendcomm import FrontendComm from spyder_kernels.comms.decorators import ( register_comm_handlers, comm_handler) @@ -77,6 +78,18 @@ def __init__(self, *args, **kwargs): # Socket to signal shell_stream locally self.loopback_socket = None + @property + def kernel_info(self): + # Used for checking correct version by spyder + infos = super().kernel_info + infos.update({ + "spyder_kernels_info": ( + spyder_kernels.__version__, + sys.executable + ) + }) + return infos + # -- Public API ----------------------------------------------------------- def frontend_call(self, blocking=False, broadcast=True, timeout=None, callback=None, display_error=False): @@ -620,6 +633,22 @@ def set_configuration(self, dic): elif value == "light": self.shell.run_line_magic("colors", "lightbg") self.set_sympy_forecolor(background_color='light') + elif key == "jedi_completer": + self.set_jedi_completer(value) + elif key == "greedy_completer": + self.set_greedy_completer(value) + elif key == "autocall": + self.set_autocall(value) + elif key == "matplotlib_backend": + self.set_matplotlib_backend(*value) + elif key == "mpl_inline_figure_format": + self.set_mpl_inline_figure_format(value) + elif key == "mpl_inline_resolution": + self.set_mpl_inline_resolution(value) + elif key == "mpl_inline_figure_size": + self.set_mpl_inline_figure_size(*value) + elif key == "mpl_inline_bbox_inches": + self.set_mpl_inline_bbox_inches(value) return ret def get_cwd(self): diff --git a/spyder_kernels/console/shell.py b/spyder_kernels/console/shell.py index 098a9754..563bd5b8 100644 --- a/spyder_kernels/console/shell.py +++ b/spyder_kernels/console/shell.py @@ -23,7 +23,6 @@ from ipykernel.zmqshell import ZMQInteractiveShell # Local imports -import spyder_kernels from spyder_kernels.customize.namespace_manager import NamespaceManager from spyder_kernels.customize.spyderpdb import SpyderPdb from spyder_kernels.customize.code_runner import SpyderCodeRunner @@ -55,12 +54,6 @@ def __init__(self, *args, **kwargs): self._allow_kbdint = False self.register_debugger_sigint() - # Used for checking correct version by spyder - self._spyder_kernels_version = ( - spyder_kernels.__version__, - sys.executable - ) - # register post_execute self.events.register('post_execute', self.do_post_execute) diff --git a/spyder_kernels/customize/spyderpdb.py b/spyder_kernels/customize/spyderpdb.py index 0d26986d..b9d76f63 100755 --- a/spyder_kernels/customize/spyderpdb.py +++ b/spyder_kernels/customize/spyderpdb.py @@ -861,7 +861,7 @@ def get_pdb_state(self): hidden = self.hidden_frames(self.stack) pdb_stack = [f for f, h in zip(pdb_stack, hidden) if not h] # Adjust the index - pdb_index -= sum([bool(i) for i in hidden[:pdb_index]]) + pdb_index -= sum(hidden[:pdb_index]) state['stack'] = (pdb_stack, pdb_index) From 1fa5275e7f86849ad9561bf1638f560a294cf94e Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 11 Sep 2023 13:09:41 +0200 Subject: [PATCH 07/27] fix index --- spyder_kernels/customize/spyderpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/customize/spyderpdb.py b/spyder_kernels/customize/spyderpdb.py index b9d76f63..0d26986d 100755 --- a/spyder_kernels/customize/spyderpdb.py +++ b/spyder_kernels/customize/spyderpdb.py @@ -861,7 +861,7 @@ def get_pdb_state(self): hidden = self.hidden_frames(self.stack) pdb_stack = [f for f, h in zip(pdb_stack, hidden) if not h] # Adjust the index - pdb_index -= sum(hidden[:pdb_index]) + pdb_index -= sum([bool(i) for i in hidden[:pdb_index]]) state['stack'] = (pdb_stack, pdb_index) From adc77f07593fc460e14227bee0aa20b942423624 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 16 Sep 2023 15:55:03 +0200 Subject: [PATCH 08/27] matplotlib --- spyder_kernels/comms/frontendcomm.py | 6 +- spyder_kernels/comms/utils.py | 5 +- spyder_kernels/console/kernel.py | 199 +++++++++++------- spyder_kernels/console/shell.py | 11 +- spyder_kernels/console/start.py | 76 +------ .../console/tests/test_console_kernel.py | 8 +- spyder_kernels/customize/code_runner.py | 4 +- spyder_kernels/customize/tests/test_umr.py | 24 +-- spyder_kernels/customize/umr.py | 62 +----- spyder_kernels/utils/mpl.py | 27 +-- 10 files changed, 158 insertions(+), 264 deletions(-) diff --git a/spyder_kernels/comms/frontendcomm.py b/spyder_kernels/comms/frontendcomm.py index 1e7e86f4..9eaf2ee1 100644 --- a/spyder_kernels/comms/frontendcomm.py +++ b/spyder_kernels/comms/frontendcomm.py @@ -130,9 +130,11 @@ def _check_comm_reply(self): """ Send comm message to frontend to check if the iopub channel is ready """ - if len(self._pending_comms) == 0: + # Make sure the length doesn't change during iteration + pending_comms = list(self._pending_comms.values()) + if len(pending_comms) == 0: return - for comm in self._pending_comms.values(): + for comm in pending_comms: self._notify_comm_ready(comm) self.kernel.io_loop.call_later(1, self._check_comm_reply) diff --git a/spyder_kernels/comms/utils.py b/spyder_kernels/comms/utils.py index 68ea8c57..dc6b8c99 100644 --- a/spyder_kernels/comms/utils.py +++ b/spyder_kernels/comms/utils.py @@ -67,11 +67,8 @@ def __call__(self, string): if not self._warning_shown: self._warning_shown = True - # Don't print handler name for `show_mpl_backend_errors` - # because we have a specific message for it. # request_pdb_stop is expected to print messages. - if self._name not in [ - 'show_mpl_backend_errors', 'request_pdb_stop']: + if self._name not in ['request_pdb_stop']: self._write( "\nOutput from spyder call " + repr(self._name) + ":\n" ) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index a26e4c44..66a3f5bb 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -34,8 +34,7 @@ from spyder_kernels.comms.decorators import ( register_comm_handlers, comm_handler) from spyder_kernels.utils.iofuncs import iofunctions -from spyder_kernels.utils.mpl import ( - MPL_BACKENDS_FROM_SPYDER, MPL_BACKENDS_TO_SPYDER, INLINE_FIGURE_FORMATS) +from spyder_kernels.utils.mpl import automatic_backend, MPL_BACKENDS_TO_SPYDER from spyder_kernels.utils.nsview import ( get_remote_data, make_remote_view, get_size) from spyder_kernels.console.shell import SpyderShell @@ -66,7 +65,6 @@ def __init__(self, *args, **kwargs): register_comm_handlers(self.shell, self.frontend_comm) self.namespace_view_settings = {} - self._mpl_backend_error = None self.faulthandler_handle = None self._cwd_initialised = False @@ -536,7 +534,7 @@ def get_mpl_interactive_backend(self): if framework is None: # Since no interactive backend has been set yet, this is # equivalent to having the inline one. - return 0 + return 'inline' elif framework in mapping: return MPL_BACKENDS_TO_SPYDER[mapping[framework]] else: @@ -545,25 +543,38 @@ def get_mpl_interactive_backend(self): # magic but not through our Preferences. return -1 - def set_matplotlib_backend(self, backend, pylab=False): - """Set matplotlib backend given a Spyder backend option.""" - mpl_backend = MPL_BACKENDS_FROM_SPYDER[str(backend)] - self._set_mpl_backend(mpl_backend, pylab=pylab) - - def set_mpl_inline_figure_format(self, figure_format): - """Set the inline figure format to use with matplotlib.""" - mpl_figure_format = INLINE_FIGURE_FORMATS[figure_format] - self._set_config_option( - 'InlineBackend.figure_format', mpl_figure_format) - - def set_mpl_inline_resolution(self, resolution): - """Set inline figure resolution.""" - self._set_mpl_inline_rc_config('figure.dpi', resolution) + @comm_handler + def set_matplotlib_conf(self, conf): + """Set matplotlib configuration""" + pylab_autoload_n = 'pylab/autoload' + pylab_backend_n = 'pylab/backend' + figure_format_n = 'pylab/inline/figure_format' + resolution_n = 'pylab/inline/resolution' + width_n = 'pylab/inline/width' + height_n = 'pylab/inline/height' + bbox_inches_n = 'pylab/inline/bbox_inches' + inline_backend = 'inline' + + if pylab_autoload_n in conf or pylab_backend_n in conf: + self._set_mpl_backend( + conf.get(pylab_backend_n, inline_backend), + pylab=conf.get(pylab_autoload_n, False) + ) + if figure_format_n in conf: + self._set_config_option( + 'InlineBackend.figure_format', + conf[figure_format_n] + ) + if resolution_n in conf: + self._set_mpl_inline_rc_config('figure.dpi', conf[resolution_n]) + if width_n in conf and height_n in conf: + self._set_mpl_inline_rc_config( + 'figure.figsize', + (conf[width_n], conf[height_n]) + ) + if bbox_inches_n in conf: + self.set_mpl_inline_bbox_inches(conf[bbox_inches_n]) - def set_mpl_inline_figure_size(self, width, height): - """Set inline figure size.""" - value = (width, height) - self._set_mpl_inline_rc_config('figure.figsize', value) def set_mpl_inline_bbox_inches(self, bbox_inches): """ @@ -621,36 +632,31 @@ def set_configuration(self, dic): self.shell.set_pdb_configuration(value) elif key == "faulthandler": ret[key] = self.enable_faulthandler() - elif key == "show_mpl_backend_errors": - self.show_mpl_backend_errors() - elif key == "check_special_kernel": - ret[key] = self.check_special_kernel() + elif key == "special_kernel": + ret[key] = self.set_special_kernel(value) elif key == "color scheme": - if value == "dark": - # Needed to change the colors of tracebacks - self.shell.run_line_magic("colors", "linux") - self.set_sympy_forecolor(background_color='dark') - elif value == "light": - self.shell.run_line_magic("colors", "lightbg") - self.set_sympy_forecolor(background_color='light') + self.set_color_scheme(value) elif key == "jedi_completer": self.set_jedi_completer(value) elif key == "greedy_completer": self.set_greedy_completer(value) elif key == "autocall": self.set_autocall(value) - elif key == "matplotlib_backend": - self.set_matplotlib_backend(*value) - elif key == "mpl_inline_figure_format": - self.set_mpl_inline_figure_format(value) - elif key == "mpl_inline_resolution": - self.set_mpl_inline_resolution(value) - elif key == "mpl_inline_figure_size": - self.set_mpl_inline_figure_size(*value) - elif key == "mpl_inline_bbox_inches": - self.set_mpl_inline_bbox_inches(value) + elif key == "matplotlib": + self.set_matplotlib_conf(value) + elif key == "update_gui": + self.shell.update_gui_frontend = value return ret + def set_color_scheme(self, color_scheme): + if color_scheme == "dark": + # Needed to change the colors of tracebacks + self.shell.run_line_magic("colors", "linux") + self.set_sympy_forecolor(background_color='dark') + elif color_scheme == "light": + self.shell.run_line_magic("colors", "lightbg") + self.set_sympy_forecolor(background_color='light') + def get_cwd(self): """Get current working directory.""" try: @@ -677,27 +683,66 @@ def close_all_mpl_figures(self): except: pass - def check_special_kernel(self): + def set_special_kernel(self, special): """ Check if optional dependencies are available for special consoles. """ - try: - if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True': - import matplotlib - elif os.environ.get('SPY_SYMPY_O') == 'True': - import sympy - elif os.environ.get('SPY_RUN_CYTHON') == 'True': - import cython - except Exception: - # Use Exception instead of ImportError here because modules can - # fail to be imported due to a lot of issues. - if os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True': - return u'matplotlib' - elif os.environ.get('SPY_SYMPY_O') == 'True': - return u'sympy' - elif os.environ.get('SPY_RUN_CYTHON') == 'True': - return u'cython' - return None + self.shell.special = None + if special is None: + return + + if special == "pylab": + try: + import matplotlib + except Exception: + return "matplotlib" + self.do_execute("from pylab import *", silent=True) + self.shell.special = special + return + + if special == "sympy": + try: + import sympy + except Exception: + return "sympy" + sympy_init = "\n".join([ + "from sympy import *", + "x, y, z, t = symbols('x y z t')", + "k, m, n = symbols('k m n', integer=True)", + "f, g, h = symbols('f g h', cls=Function)", + "init_printing()", + ]) + self.do_execute(sympy_init, silent=True) + self.shell.special = special + return + + if special == "cython": + try: + import cython + + # Import pyximport to enable Cython files support for + # import statement + import pyximport + pyx_setup_args = {} + + # Add Numpy include dir to pyximport/distutils + try: + import numpy + pyx_setup_args['include_dirs'] = numpy.get_include() + except Exception: + pass + + # Setup pyximport and enable Cython files reload + pyximport.install(setup_args=pyx_setup_args, + reload_support=True) + except Exception: + return "cython" + + self.shell.run_line_magic("reload_ext", "Cython") + self.shell.special = special + return + + raise NotImplementedError(f"{special}") @comm_handler def update_syspath(self, path_dict, new_path_dict): @@ -850,6 +895,9 @@ def _set_mpl_backend(self, backend, pylab=False): magic = 'pylab' if pylab else 'matplotlib' + if backend == "auto": + backend = automatic_backend() + error = None try: # This prevents Matplotlib to automatically set the backend, which @@ -888,8 +936,8 @@ def _set_mpl_backend(self, backend, pylab=False): error = generic_error.format(err) + '\n\n' + additional_info except Exception: error = generic_error.format(traceback.format_exc()) - - self._mpl_backend_error = error + if error: + print(error) def _set_config_option(self, option, value): """ @@ -921,23 +969,18 @@ def _set_mpl_inline_rc_config(self, option, value): # Needed in case matplolib isn't installed pass - def show_mpl_backend_errors(self): - """Show Matplotlib backend errors after the prompt is ready.""" - if self._mpl_backend_error is not None: - print(self._mpl_backend_error) # spyder: test-skip - - @comm_handler def set_sympy_forecolor(self, background_color='dark'): """Set SymPy forecolor depending on console background.""" - if os.environ.get('SPY_SYMPY_O') == 'True': - try: - from sympy import init_printing - if background_color == 'dark': - init_printing(forecolor='White', ip=self.shell) - elif background_color == 'light': - init_printing(forecolor='Black', ip=self.shell) - except Exception: - pass + if self.shell.special != "sympy": + return + try: + from sympy import init_printing + if background_color == 'dark': + init_printing(forecolor='White', ip=self.shell) + elif background_color == 'light': + init_printing(forecolor='Black', ip=self.shell) + except Exception: + pass # --- Others def _load_autoreload_magic(self): diff --git a/spyder_kernels/console/shell.py b/spyder_kernels/console/shell.py index 563bd5b8..3614370f 100644 --- a/spyder_kernels/console/shell.py +++ b/spyder_kernels/console/shell.py @@ -49,10 +49,12 @@ def __init__(self, *args, **kwargs): # Create _namespace_stack before __init__ self._namespace_stack = [] self._request_pdb_stop = False + self.special = None self._pdb_conf = {} super(SpyderShell, self).__init__(*args, **kwargs) self._allow_kbdint = False self.register_debugger_sigint() + self.update_gui_frontend = False # register post_execute self.events.register('post_execute', self.do_post_execute) @@ -86,10 +88,11 @@ def enable_matplotlib(self, gui=None): if gui is None or gui.lower() == "auto": gui = automatic_backend() gui, backend = super(SpyderShell, self).enable_matplotlib(gui) - try: - self.kernel.frontend_call(blocking=False).update_matplotlib_gui(gui) - except Exception: - pass + if self.update_gui_frontend: + try: + self.kernel.frontend_call(blocking=False).update_matplotlib_gui(gui) + except Exception: + pass return gui, backend # --- For Pdb namespace integration diff --git a/spyder_kernels/console/start.py b/spyder_kernels/console/start.py index 708319c0..0502a134 100644 --- a/spyder_kernels/console/start.py +++ b/spyder_kernels/console/start.py @@ -21,8 +21,6 @@ # Local imports from spyder_kernels.utils.misc import is_module_installed -from spyder_kernels.utils.mpl import ( - MPL_BACKENDS_FROM_SPYDER, INLINE_FIGURE_FORMATS) def import_spydercustomize(): @@ -48,24 +46,6 @@ def import_spydercustomize(): except ValueError: pass - -def sympy_config(mpl_backend): - """Sympy configuration""" - if mpl_backend is not None: - lines = """ -from sympy.interactive import init_session -init_session() -%matplotlib {0} -""".format(mpl_backend) - else: - lines = """ -from sympy.interactive import init_session -init_session() -""" - - return lines - - def kernel_config(): """Create a config object with IPython kernel options.""" from IPython.core.application import get_ipython_dir @@ -150,56 +130,10 @@ def kernel_config(): 'figure.edgecolor': 'white' } - # Pylab configuration - mpl_backend = None if is_module_installed('matplotlib'): - # Set Matplotlib backend with Spyder options - pylab_o = os.environ.get('SPY_PYLAB_O') - backend_o = os.environ.get('SPY_BACKEND_O') - if pylab_o == 'True' and backend_o is not None: - mpl_backend = MPL_BACKENDS_FROM_SPYDER[backend_o] - # Inline backend configuration - if mpl_backend == 'inline': - # Figure format - format_o = os.environ.get('SPY_FORMAT_O') - formats = INLINE_FIGURE_FORMATS - if format_o is not None: - spy_cfg.InlineBackend.figure_format = formats[format_o] - - # Resolution - resolution_o = os.environ.get('SPY_RESOLUTION_O') - if resolution_o is not None: - spy_cfg.InlineBackend.rc['figure.dpi'] = float( - resolution_o) - - # Figure size - width_o = float(os.environ.get('SPY_WIDTH_O')) - height_o = float(os.environ.get('SPY_HEIGHT_O')) - if width_o is not None and height_o is not None: - spy_cfg.InlineBackend.rc['figure.figsize'] = (width_o, - height_o) - - # Print figure kwargs - bbox_inches_o = os.environ.get('SPY_BBOX_INCHES_O') - bbox_inches = 'tight' if bbox_inches_o == 'True' else None - spy_cfg.InlineBackend.print_figure_kwargs.update( - {'bbox_inches': bbox_inches}) - else: - # Set Matplotlib backend to inline for external kernels. - # Fixes issue 108 - mpl_backend = 'inline' - - # Automatically load Pylab and Numpy, or only set Matplotlib - # backend - autoload_pylab_o = os.environ.get('SPY_AUTOLOAD_PYLAB_O') == 'True' - command = "get_ipython().kernel._set_mpl_backend('{0}', {1})" spy_cfg.IPKernelApp.exec_lines.append( - command.format(mpl_backend, autoload_pylab_o)) - - # Enable Cython magic - run_cython = os.environ.get('SPY_RUN_CYTHON') == 'True' - if run_cython and is_module_installed('Cython'): - spy_cfg.IPKernelApp.exec_lines.append('%reload_ext Cython') + "get_ipython().kernel._set_mpl_backend('inline')" + ) # Run a file at startup use_file_o = os.environ.get('SPY_USE_FILE_O') @@ -220,12 +154,6 @@ def kernel_config(): greedy_o = os.environ.get('SPY_GREEDY_O') == 'True' spy_cfg.IPCompleter.greedy = greedy_o - # Sympy loading - sympy_o = os.environ.get('SPY_SYMPY_O') == 'True' - if sympy_o and is_module_installed('sympy'): - lines = sympy_config(mpl_backend) - spy_cfg.IPKernelApp.exec_lines.append(lines) - # Disable the new mechanism to capture and forward low-level output # in IPykernel 6. For that we have Wurlitzer. spy_cfg.IPKernelApp.capture_fd_output = False diff --git a/spyder_kernels/console/tests/test_console_kernel.py b/spyder_kernels/console/tests/test_console_kernel.py index 65870f02..b7f17cce 100644 --- a/spyder_kernels/console/tests/test_console_kernel.py +++ b/spyder_kernels/console/tests/test_console_kernel.py @@ -32,7 +32,6 @@ # Local imports from spyder_kernels.utils.iofuncs import iofunctions -from spyder_kernels.utils.mpl import MPL_BACKENDS_FROM_SPYDER from spyder_kernels.utils.test_utils import get_kernel, get_log_text from spyder_kernels.customize.spyderpdb import SpyderPdb from spyder_kernels.comms.commbase import CommBase @@ -1169,11 +1168,14 @@ def test_get_interactive_backend(backend): user_expressions = reply['content']['user_expressions'] value = user_expressions['output']['data']['text/plain'] + # remove quotes + value = value[1:-1] + # Assert we got the right interactive backend if backend is not None: - assert MPL_BACKENDS_FROM_SPYDER[value] == backend + assert value == backend else: - assert value == '0' + assert value == 'inline' def test_global_message(tmpdir): diff --git a/spyder_kernels/customize/code_runner.py b/spyder_kernels/customize/code_runner.py index f4321eaa..a2c7fde4 100644 --- a/spyder_kernels/customize/code_runner.py +++ b/spyder_kernels/customize/code_runner.py @@ -266,7 +266,7 @@ def _exec_file( """ Execute a file. """ - if self.umr.enabled: + if self.umr.enabled and self.shell.special != "cython": self.umr.run() if args is not None and not isinstance(args, str): raise TypeError("expected a character buffer object") @@ -324,7 +324,7 @@ def _exec_file( print("Working directory {} doesn't exist.\n".format(wdir)) try: - if self.umr.has_cython: + if self.shell.special == "cython": # Cython files with io.open(filename, encoding="utf-8") as f: self.shell.run_cell_magic("cython", "", f.read()) diff --git a/spyder_kernels/customize/tests/test_umr.py b/spyder_kernels/customize/tests/test_umr.py index 438f7eff..c237ccc7 100644 --- a/spyder_kernels/customize/tests/test_umr.py +++ b/spyder_kernels/customize/tests/test_umr.py @@ -39,27 +39,6 @@ def square(x): return create_module -def test_umr_skip_cython(user_module): - """ - Test that the UMR doesn't try to reload modules when Cython - support is active. - """ - # Create user module - user_module('foo') - - # Activate Cython support - os.environ['SPY_RUN_CYTHON'] = 'True' - - # Create UMR - umr = UserModuleReloader() - - import foo - assert umr.is_module_reloadable(foo, 'foo') == False - - # Deactivate Cython support - os.environ['SPY_RUN_CYTHON'] = 'False' - - def test_umr_run(user_module): """Test that UMR's run method is working correctly.""" # Create user module @@ -72,8 +51,7 @@ def test_umr_run(user_module): umr = UserModuleReloader() from foo1.bar import square - umr.run() - umr.modnames_to_reload == ['foo', 'foo.bar'] + assert umr.run() == ['foo', 'foo.bar'] def test_umr_previous_modules(user_module): diff --git a/spyder_kernels/customize/umr.py b/spyder_kernels/customize/umr.py index fa140020..57952f2b 100644 --- a/spyder_kernels/customize/umr.py +++ b/spyder_kernels/customize/umr.py @@ -49,13 +49,6 @@ def __init__(self, namelist=None, pathlist=None): # List of previously loaded modules self.previous_modules = list(sys.modules.keys()) - # List of module names to reload - self.modnames_to_reload = [] - - # Activate Cython support - self.has_cython = False - self.activate_cython() - # Check if the UMR is enabled or not enabled = os.environ.get("SPY_UMR_ENABLED", "") self.enabled = enabled.lower() == "true" @@ -66,54 +59,17 @@ def __init__(self, namelist=None, pathlist=None): def is_module_reloadable(self, module, modname): """Decide if a module is reloadable or not.""" - if self.has_cython: - # Don't return cached inline compiled .PYX files + if (path_is_library(getattr(module, '__file__', None), + self.pathlist) or + self.is_module_in_namelist(modname)): return False else: - if (path_is_library(getattr(module, '__file__', None), - self.pathlist) or - self.is_module_in_namelist(modname)): - return False - else: - return True + return True def is_module_in_namelist(self, modname): """Decide if a module can be reloaded or not according to its name.""" return set(modname.split('.')) & set(self.namelist) - def activate_cython(self): - """ - Activate Cython support. - - We need to run this here because if the support is - active, we don't to run the UMR at all. - """ - run_cython = os.environ.get("SPY_RUN_CYTHON") == "True" - - if run_cython: - try: - __import__('Cython') - self.has_cython = True - except Exception: - pass - - if self.has_cython: - # Import pyximport to enable Cython files support for - # import statement - import pyximport - pyx_setup_args = {} - - # Add Numpy include dir to pyximport/distutils - try: - import numpy - pyx_setup_args['include_dirs'] = numpy.get_include() - except Exception: - pass - - # Setup pyximport and enable Cython files reload - pyximport.install(setup_args=pyx_setup_args, - reload_support=True) - def run(self): """ Delete user modules to force Python to deeply reload them @@ -122,18 +78,20 @@ def run(self): modules installed in subdirectories of Python interpreter's binary Do not del C modules """ - self.modnames_to_reload = [] + modnames_to_reload = [] for modname, module in list(sys.modules.items()): if modname not in self.previous_modules: # Decide if a module can be reloaded or not if self.is_module_reloadable(module, modname): - self.modnames_to_reload.append(modname) + modnames_to_reload.append(modname) del sys.modules[modname] else: continue # Report reloaded modules - if self.verbose and self.modnames_to_reload: - modnames = self.modnames_to_reload + if self.verbose and modnames_to_reload: + modnames = modnames_to_reload print("\x1b[4;33m%s\x1b[24m%s\x1b[0m" % ("Reloaded modules", ": "+", ".join(modnames))) + + return modnames_to_reload diff --git a/spyder_kernels/utils/mpl.py b/spyder_kernels/utils/mpl.py index 7927e49d..7756fd39 100644 --- a/spyder_kernels/utils/mpl.py +++ b/spyder_kernels/utils/mpl.py @@ -11,13 +11,6 @@ from spyder_kernels.utils.misc import is_module_installed -# Mapping of inline figure formats -INLINE_FIGURE_FORMATS = { - '0': 'png', - '1': 'svg' -} - - # Inline backend if is_module_installed('matplotlib_inline'): inline_backend = 'module://matplotlib_inline.backend_inline' @@ -27,11 +20,11 @@ # Mapping of matlotlib backends options to Spyder MPL_BACKENDS_TO_SPYDER = { - inline_backend: 0, - 'Qt5Agg': 2, - 'QtAgg': 2, # For Matplotlib 3.5+ - 'TkAgg': 3, - 'MacOSX': 4, + inline_backend: "inline", + 'Qt5Agg': 'qt5', + 'QtAgg': 'qt5', # For Matplotlib 3.5+ + 'TkAgg': 'tk', + 'MacOSX': 'osx', } @@ -44,13 +37,3 @@ def automatic_backend(): else: auto_backend = 'inline' return auto_backend - - -# Mapping of Spyder options to backends -MPL_BACKENDS_FROM_SPYDER = { - '0': 'inline', - '1': automatic_backend(), - '2': 'qt5', - '3': 'tk', - '4': 'osx' -} From 6acf6e45302cc49c163e31f659fa660e8ed00e18 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 16 Sep 2023 20:41:40 +0200 Subject: [PATCH 09/27] fix test --- spyder_kernels/console/kernel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index 66a3f5bb..b8b5bd89 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -696,7 +696,7 @@ def set_special_kernel(self, special): import matplotlib except Exception: return "matplotlib" - self.do_execute("from pylab import *", silent=True) + exec("from pylab import *", self.shell.user_ns) self.shell.special = special return @@ -712,7 +712,7 @@ def set_special_kernel(self, special): "f, g, h = symbols('f g h', cls=Function)", "init_printing()", ]) - self.do_execute(sympy_init, silent=True) + exec(sympy_init, self.shell.user_ns) self.shell.special = special return From 6ca7b7a9526a04c9059fbe7b319e2db31c16b097 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 8 Oct 2023 12:30:10 -0500 Subject: [PATCH 10/27] Restore compatibility with Python 2 This was broken in the 2.4 series. --- spyder_kernels/comms/frontendcomm.py | 9 +++++++-- spyder_kernels/console/start.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spyder_kernels/comms/frontendcomm.py b/spyder_kernels/comms/frontendcomm.py index 93b99eec..edf4adb8 100644 --- a/spyder_kernels/comms/frontendcomm.py +++ b/spyder_kernels/comms/frontendcomm.py @@ -24,6 +24,10 @@ from spyder_kernels.py3compat import TimeoutError, PY2 +if PY2: + import thread + + def get_free_port(): """Find a free port on the local machine.""" sock = socket.socket() @@ -264,7 +268,7 @@ def _remote_callback(self, call_name, call_args, call_kwargs): current_stderr = sys.stderr saved_stdout_write = current_stdout.write saved_stderr_write = current_stderr.write - thread_id = threading.get_ident() + thread_id = thread.get_ident() if PY2 else threading.get_ident() current_stdout.write = WriteWrapper( saved_stdout_write, call_name, thread_id) current_stderr.write = WriteWrapper( @@ -300,7 +304,8 @@ def is_benign_message(self, message): def __call__(self, string): """Print warning once.""" - if self._thread_id != threading.get_ident(): + thread_id = thread.get_ident() if PY2 else threading.get_ident() + if self._thread_id != thread_id: return self._write(string) if not self.is_benign_message(string): diff --git a/spyder_kernels/console/start.py b/spyder_kernels/console/start.py index 02a2710a..f80111d7 100644 --- a/spyder_kernels/console/start.py +++ b/spyder_kernels/console/start.py @@ -239,7 +239,8 @@ def kernel_config(): # Disable the new mechanism to capture and forward low-level output # in IPykernel 6. For that we have Wurlitzer. - spy_cfg.IPKernelApp.capture_fd_output = False + if not PY2: + spy_cfg.IPKernelApp.capture_fd_output = False # Merge IPython and Spyder configs. Spyder prefs will have prevalence # over IPython ones From 884d733ea875e8a8455d2816202dd087bee53857 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 9 Oct 2023 10:19:24 -0500 Subject: [PATCH 11/27] Improve the way we depend on IPython and IPykernel per Python version That will allow us to handle those dependencies better for Python 3.8+ --- requirements/posix.txt | 2 +- requirements/windows.txt | 2 +- setup.py | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/requirements/posix.txt b/requirements/posix.txt index 1cf65023..be22b772 100644 --- a/requirements/posix.txt +++ b/requirements/posix.txt @@ -1,6 +1,6 @@ cloudpickle ipykernel>=6.23.2,<7 -ipython>=7.31.1,<9 +ipython>=8.12.2,<9 jupyter_client>=7.4.9,<9 pyzmq>=22.1.0 wurlitzer>=1.0.3 diff --git a/requirements/windows.txt b/requirements/windows.txt index d87d5415..ec6e8483 100644 --- a/requirements/windows.txt +++ b/requirements/windows.txt @@ -1,5 +1,5 @@ cloudpickle ipykernel>=6.23.2,<7 -ipython>=7.31.1,<9 +ipython>=8.12.2,<9 jupyter_client>=7.4.9,<9 pyzmq>=22.1.0 diff --git a/setup.py b/setup.py index e874f1c1..9de57ea6 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,12 @@ def get_version(module='spyder_kernels'): 'backports.functools-lru-cache; python_version<"3"', 'cloudpickle', 'ipykernel>=4.5,<5; python_version<"3"', - 'ipykernel>=6.23.2,<7; python_version>="3"', + 'ipykernel>=6.16.1,<6.17; python_version<"3.8"', + 'ipykernel>=6.23.2,<7; python_version>="3.8"', 'ipython<6; python_version<"3"', - 'ipython>=7.31.1,<9,!=8.8.0,!=8.9.0,!=8.10.0,!=8.11.0,!=8.12.0,!=8.12.1; python_version>="3"', + 'ipython>=7.31.1,<8; python_version<"3.8"', + 'ipython>=8.12.2,<8.13; python_version=="3.8"', + 'ipython>=8.13.0,<9; python_version>"3.8"', 'jupyter-client>=5.3.4,<6; python_version<"3"', 'jupyter-client>=7.4.9,<9; python_version>="3"', 'pyzmq>=17,<20; python_version<"3"', @@ -92,10 +95,12 @@ def get_version(module='spyder_kernels'): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Software Development :: Interpreters', ] ) From 9560f09d5c0562b2cc75cadda23348359f7a2df9 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 10 Oct 2023 17:48:46 -0500 Subject: [PATCH 12/27] Debugger: Add support for chained exceptions --- spyder_kernels/customize/spyderpdb.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spyder_kernels/customize/spyderpdb.py b/spyder_kernels/customize/spyderpdb.py index f988f252..7f687e5c 100755 --- a/spyder_kernels/customize/spyderpdb.py +++ b/spyder_kernels/customize/spyderpdb.py @@ -320,20 +320,32 @@ def interaction(self, frame, traceback): If this is from sigint, break on the upper frame. If the frame is in spydercustomize.py, quit. Notifies spyder and print current code. - """ if self._pdb_breaking: self._pdb_breaking = False if frame and frame.f_back: return self.interaction(frame.f_back, traceback) + # This is necessary to handle chained exceptions in Pdb, support for + # which was added in IPython 8.15 and will be the default in Python + # 3.13 (see ipython/ipython#14146). + if isinstance(traceback, BaseException): + _chained_exceptions, tb = self._get_tb_and_exceptions(traceback) + + with self._hold_exceptions(_chained_exceptions): + self.interaction(frame, tb) + + return + self.setup(frame, traceback) self.print_stack_entry(self.stack[self.curindex]) + if self._frontend_notified: self._cmdloop() else: with DebugWrapper(self): self._cmdloop() + self.forget() def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', From 3ffd051af698ef964d74ad016da82a56c6944773 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 12 Oct 2023 13:49:44 -0500 Subject: [PATCH 13/27] Utils: Improve the way we get signatures in getsignaturefromtext - The regular expressions we were using were too complex and failed to get signatures in several situations. - Also, slightly improve code style and docstrings of other methods. --- spyder_kernels/utils/dochelpers.py | 54 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/spyder_kernels/utils/dochelpers.py b/spyder_kernels/utils/dochelpers.py index 336a606c..db986ad6 100644 --- a/spyder_kernels/utils/dochelpers.py +++ b/spyder_kernels/utils/dochelpers.py @@ -171,40 +171,50 @@ def getsource(obj): def getsignaturefromtext(text, objname): - """Get object signatures from text (object documentation) - Return a list containing a single string in most cases - Example of multiple signatures: PyQt5 objects""" + """Get object signature from text (i.e. object documentation).""" if isinstance(text, dict): text = text.get('docstring', '') + # Regexps - oneline_re = objname + r'\([^\)].+?(?<=[\w\]\}\'"])\)(?!,)' - multiline_re = objname + r'\([^\)]+(?<=[\w\]\}\'"])\)(?!,)' - multiline_end_parenleft_re = r'(%s\([^\)]+(\),\n.+)+(?<=[\w\]\}\'"])\))' + args_re = r'(\(.+?\))' + if objname: + signature_re = objname + args_re + else: + identifier_re = r'(\w+)' + signature_re = identifier_re + args_re + # Grabbing signatures if not text: text = '' - sigs_1 = re.findall(oneline_re + '|' + multiline_re, text) - sigs_2 = [g[0] for g in re.findall(multiline_end_parenleft_re % objname, text)] - all_sigs = sigs_1 + sigs_2 + + sigs = re.findall(signature_re, text) + # The most relevant signature is usually the first one. There could be - # others in doctests but those are not so important - if all_sigs: - return all_sigs[0] - else: - return '' + # others in doctests or other places, but those are not so important. + sig = '' + if sigs: + if PY2: + # We don't have an easy way to check if the identifier detected by + # signature_re is a valid one in Python 2. So, we simply select the + # first match. + sig = sigs[0] if objname else sigs[0][1] + else: + if objname: + sig = sigs[0] + else: + valid_sigs = [s for s in sigs if s[0].isidentifier()] + if valid_sigs: + sig = valid_sigs[0][1] -# Fix for Issue 1953 -# TODO: Add more signatures and remove this hack in 2.4 -getsignaturesfromtext = getsignaturefromtext + return sig def getargspecfromtext(text): """ Try to get the formatted argspec of a callable from the first block of its - docstring + docstring. - This will return something like - '(foo, bar, k=1)' + This will return something like `(x, y, k=1)`. """ blocks = text.split("\n\n") first_block = blocks[0].strip() @@ -212,10 +222,10 @@ def getargspecfromtext(text): def getargsfromtext(text, objname): - """Get arguments from text (object documentation)""" + """Get arguments from text (object documentation).""" signature = getsignaturefromtext(text, objname) if signature: - argtxt = signature[signature.find('(')+1:-1] + argtxt = signature[signature.find('(') + 1:-1] return argtxt.split(',') From 212e325d11d9b9c9023681d350ccb4c0b11b3aa9 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 12 Oct 2023 13:56:45 -0500 Subject: [PATCH 14/27] Utils: Catch error when trying to get the signature of some objects Also, remove code that is already used in our tests. --- spyder_kernels/utils/dochelpers.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/spyder_kernels/utils/dochelpers.py b/spyder_kernels/utils/dochelpers.py index db986ad6..426cb9dc 100644 --- a/spyder_kernels/utils/dochelpers.py +++ b/spyder_kernels/utils/dochelpers.py @@ -120,7 +120,15 @@ def getdoc(obj): args, varargs, varkw, defaults, formatvalue=lambda o:'='+repr(o)) else: - sig = inspect.signature(obj) + # This is necessary to catch errors for objects without a + # signature, like numpy.where. + # Fixes spyder-ide/spyder#21148 + try: + sig = inspect.signature(obj) + except ValueError: + sig = getargspecfromtext(doc['docstring']) + if not sig: + sig = '(...)' doc['argspec'] = str(sig) if name == '': doc['name'] = name + ' lambda ' @@ -340,20 +348,3 @@ def isdefined(obj, force_import=False, namespace=None): return False base += '.'+attr return True - - -if __name__ == "__main__": - class Test(object): - def method(self, x, y=2): - pass - print(getargtxt(Test.__init__)) # spyder: test-skip - print(getargtxt(Test.method)) # spyder: test-skip - print(isdefined('numpy.take', force_import=True)) # spyder: test-skip - print(isdefined('__import__')) # spyder: test-skip - print(isdefined('.keys', force_import=True)) # spyder: test-skip - print(getobj('globals')) # spyder: test-skip - print(getobj('globals().keys')) # spyder: test-skip - print(getobj('+scipy.signal.')) # spyder: test-skip - print(getobj('4.')) # spyder: test-skip - print(getdoc(sorted)) # spyder: test-skip - print(getargtxt(sorted)) # spyder: test-skip From 59a1a089eb82b3cd249dbf9de0ffd1ef231f16a0 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 12 Oct 2023 14:00:07 -0500 Subject: [PATCH 15/27] Testing: Add tests to cover the previous changes --- spyder_kernels/utils/tests/test_dochelpers.py | 83 ++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/utils/tests/test_dochelpers.py b/spyder_kernels/utils/tests/test_dochelpers.py index 220db9e2..f3fa957f 100644 --- a/spyder_kernels/utils/tests/test_dochelpers.py +++ b/spyder_kernels/utils/tests/test_dochelpers.py @@ -18,8 +18,9 @@ import pytest # Local imports -from spyder_kernels.utils.dochelpers import (getargtxt, getdoc, getobj, - isdefined) +from spyder_kernels.utils.dochelpers import ( + getargtxt, getargspecfromtext, getdoc, getobj, getsignaturefromtext, + isdefined) from spyder_kernels.py3compat import PY2 @@ -60,5 +61,83 @@ def test_dochelpers(): assert getobj('4.') == '4' +@pytest.mark.skipif(PY2, reason="Fails in Python 2") +def test_no_signature(): + """ + Test that we can get documentation for objects for which Python can't get a + signature directly because it gives an error. + + This is a regression test for issue spyder-ide/spyder#21148 + """ + import numpy as np + doc = getdoc(np.where) + signature = doc['argspec'] + assert signature and signature != "(...)" and signature.startswith("(") + assert doc['docstring'] + + +@pytest.mark.parametrize( + 'text, name, expected', + [ + ('foo(x, y)', 'foo', '(x, y)'), + ('foo(x, y)', '', '(x, y)'), + ] +) +def test_getsignaturefromtext_py2(text, name, expected): + assert getsignaturefromtext(text, name) == expected + + +@pytest.mark.skipif(PY2, reason="Don't work in Python 2") +@pytest.mark.parametrize( + 'text, name, expected', + [ + # Simple text with and without name + ('foo(x, y)', 'foo', '(x, y)'), + ('foo(x, y)', '', '(x, y)'), + # Single arg + ('foo(x)', '', '(x)'), + ('foo(x = {})', '', '(x = {})'), + # Not a valid identifier + ('1a(x, y)', '', ''), + # Valid identifier + ('a1(x, y=2)', '', '(x, y=2)'), + # Unicode identifier with and without name + ('ΣΔ(x, y)', 'ΣΔ', '(x, y)'), + ('ΣΔ(x, y)', '', '(x, y)'), + # Multiple signatures in a single line + ('ΣΔ(x, y) foo(a, b)', '', '(x, y)'), + ('1a(x, y) foo(a, b)', '', '(a, b)'), + # Multiple signatures in multiple lines + ('foo(a, b = 1)\n\nΣΔ(x, y=2)', '', '(a, b = 1)'), + ('1a(a, b = 1)\n\nΣΔ(x, y=2)', '', '(x, y=2)'), + # Signature after math operations + ('2(3 + 5) 3*(99) ΣΔ(x, y)', '', '(x, y)'), + # No identifier + ('(x, y)', '', ''), + ('foo (a=1, b = 2)', '', ''), + # Empty signature + ('foo()', '', ''), + ('foo()', 'foo', ''), + ] +) +def test_getsignaturefromtext(text, name, expected): + assert getsignaturefromtext(text, name) == expected + + +def test_multisignature(): + """ + Test that we can get at least one signature from an object with multiple + ones declared in its docstring. + """ + def foo(): + """ + foo(x, y) foo(a, b) + foo(c, d) + """ + + signature = getargspecfromtext(foo.__doc__) + assert signature == "(x, y)" + + if __name__ == "__main__": pytest.main() From cd347998c82e98ed68b47b71c34849d7fb090dc7 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 16 Oct 2023 17:33:39 -0500 Subject: [PATCH 16/27] Improve getting signatures from text - Discard IPython default signatures in case we're able to find others. - Get signatures written in multiple lines at the beginning of docstrings. --- spyder_kernels/utils/dochelpers.py | 32 +++++++++++++++++-- spyder_kernels/utils/tests/test_dochelpers.py | 17 ++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/spyder_kernels/utils/dochelpers.py b/spyder_kernels/utils/dochelpers.py index 426cb9dc..83db1a43 100644 --- a/spyder_kernels/utils/dochelpers.py +++ b/spyder_kernels/utils/dochelpers.py @@ -207,12 +207,38 @@ def getsignaturefromtext(text, objname): # first match. sig = sigs[0] if objname else sigs[0][1] else: + # Default signatures returned by IPython. + # Notes: + # * These are not real signatures but only used to provide a + # placeholder. + # * We skip them if we can find other signatures in `text`. + # * This is necessary because we also use this function in Spyder + # to parse the content of inspect replies that come from the + # kernel, which can include these signatures. + default_ipy_sigs = [ + '(*args, **kwargs)', + '(self, /, *args, **kwargs)' + ] + if objname: - sig = sigs[0] + real_sigs = [s for s in sigs if s not in default_ipy_sigs] + + if real_sigs: + sig = real_sigs[0] + else: + sig = sigs[0] else: valid_sigs = [s for s in sigs if s[0].isidentifier()] + if valid_sigs: - sig = valid_sigs[0][1] + real_sigs = [ + s for s in valid_sigs if s[1] not in default_ipy_sigs + ] + + if real_sigs: + sig = real_sigs[0][1] + else: + sig = valid_sigs[0][1] return sig @@ -225,7 +251,7 @@ def getargspecfromtext(text): This will return something like `(x, y, k=1)`. """ blocks = text.split("\n\n") - first_block = blocks[0].strip() + first_block = blocks[0].strip().replace('\n', '') return getsignaturefromtext(first_block, '') diff --git a/spyder_kernels/utils/tests/test_dochelpers.py b/spyder_kernels/utils/tests/test_dochelpers.py index f3fa957f..f67d730f 100644 --- a/spyder_kernels/utils/tests/test_dochelpers.py +++ b/spyder_kernels/utils/tests/test_dochelpers.py @@ -139,5 +139,22 @@ def foo(): assert signature == "(x, y)" +def test_multiline_signature(): + """ + Test that we can get signatures splitted into multiple lines in a + docstring. + """ + def foo(): + """ + foo(x, + y) + + This is a docstring. + """ + + signature = getargspecfromtext(foo.__doc__) + assert signature.startswith("(x, ") + + if __name__ == "__main__": pytest.main() From e540004228448699c4554baaa699e5b9808d886b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 5 Nov 2023 18:55:31 -0500 Subject: [PATCH 17/27] Skip IPython 8.17.1 in our dependencies for Python 3.9+ That's because that version broke the autoreload magic. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9de57ea6..3a417cde 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ def get_version(module='spyder_kernels'): 'ipython<6; python_version<"3"', 'ipython>=7.31.1,<8; python_version<"3.8"', 'ipython>=8.12.2,<8.13; python_version=="3.8"', - 'ipython>=8.13.0,<9; python_version>"3.8"', + 'ipython>=8.13.0,<9,!=8.17.1; python_version>"3.8"', 'jupyter-client>=5.3.4,<6; python_version<"3"', 'jupyter-client>=7.4.9,<9; python_version>="3"', 'pyzmq>=17,<20; python_version<"3"', From 4cd5c73f978919a5e1188816addbd2d99dd3590a Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 6 Nov 2023 12:19:16 -0500 Subject: [PATCH 18/27] Update Changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ace9802..8d9c4a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # History of changes +## Version 2.5.0 (2023-11-06) + +### New features + +* Add support for chained exceptions to the debugger. +* Improve getting signatures from docstrings. +* Restore compatibility with Python 2. + +### Pull Requests Merged + +* [PR 475](https://github.com/spyder-ide/spyder-kernels/pull/475) - PR: Skip IPython 8.17.1 in our dependencies for Python 3.9+, by [@ccordoba12](https://github.com/ccordoba12) +* [PR 474](https://github.com/spyder-ide/spyder-kernels/pull/474) - PR: More improvements to getting signatures from text, by [@ccordoba12](https://github.com/ccordoba12) +* [PR 473](https://github.com/spyder-ide/spyder-kernels/pull/473) - PR: Improve getting signatures from docstrings and catch error when trying to get the signature of some objects, by [@ccordoba12](https://github.com/ccordoba12) +* [PR 472](https://github.com/spyder-ide/spyder-kernels/pull/472) - PR: Add support for chained exceptions to the debugger, by [@ccordoba12](https://github.com/ccordoba12) +* [PR 471](https://github.com/spyder-ide/spyder-kernels/pull/471) - PR: Improve the way we depend on IPython and IPykernel per Python version, by [@ccordoba12](https://github.com/ccordoba12) +* [PR 469](https://github.com/spyder-ide/spyder-kernels/pull/469) - PR: Restore compatibility with Python 2, by [@ccordoba12](https://github.com/ccordoba12) + +In this release 6 pull requests were closed. + + +--- + + ## Version 2.4.4 (2023-06-29) ### Issues Closed From 6759f0aec79a87b37b59184b3aa349c8c0ed4e82 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 6 Nov 2023 12:20:32 -0500 Subject: [PATCH 19/27] Release 2.5.0 --- spyder_kernels/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/_version.py b/spyder_kernels/_version.py index 5f35e96f..f8a12fd7 100644 --- a/spyder_kernels/_version.py +++ b/spyder_kernels/_version.py @@ -8,5 +8,5 @@ """Version File.""" -VERSION_INFO = (2, 5, 0, 'dev0') +VERSION_INFO = (2, 5, 0) __version__ = '.'.join(map(str, VERSION_INFO)) From 204b78347561f6f08c4f97b8415fcb7f76685e70 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 6 Nov 2023 12:24:40 -0500 Subject: [PATCH 20/27] Back to work --- spyder_kernels/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder_kernels/_version.py b/spyder_kernels/_version.py index f8a12fd7..f4932aee 100644 --- a/spyder_kernels/_version.py +++ b/spyder_kernels/_version.py @@ -8,5 +8,5 @@ """Version File.""" -VERSION_INFO = (2, 5, 0) +VERSION_INFO = (2, 6, 0, 'dev0') __version__ = '.'.join(map(str, VERSION_INFO)) From a4f2b568e315d1a4712cd9d9a10c708bea4aad5a Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 12 Nov 2023 17:15:36 +0100 Subject: [PATCH 21/27] move autoreload and wurlitzer to comms --- spyder_kernels/console/kernel.py | 95 ++++++++++++++++++-------------- spyder_kernels/console/shell.py | 4 +- spyder_kernels/console/start.py | 26 +-------- spyder_kernels/customize/umr.py | 2 +- 4 files changed, 58 insertions(+), 69 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index b8b5bd89..cd128f6d 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -617,10 +617,10 @@ def set_autocall(self, autocall): # --- Additional methods @comm_handler - def set_configuration(self, dic): + def set_configuration(self, conf): """Set kernel configuration""" ret = {} - for key, value in dic.items(): + for key, value in conf.items(): if key == "cwd": self._cwd_initialised = True os.chdir(value) @@ -631,7 +631,8 @@ def set_configuration(self, dic): elif key == "pdb": self.shell.set_pdb_configuration(value) elif key == "faulthandler": - ret[key] = self.enable_faulthandler() + if value: + ret[key] = self.enable_faulthandler() elif key == "special_kernel": ret[key] = self.set_special_kernel(value) elif key == "color scheme": @@ -646,6 +647,11 @@ def set_configuration(self, dic): self.set_matplotlib_conf(value) elif key == "update_gui": self.shell.update_gui_frontend = value + elif key == "wurlitzer": + if value: + self._load_wurlitzer() + elif key == "autoreload_magic": + self._autoreload_magic(value) return ret def set_color_scheme(self, color_scheme): @@ -693,54 +699,55 @@ def set_special_kernel(self, special): if special == "pylab": try: - import matplotlib + import matplotlib + exec("from pylab import *", self.shell.user_ns) + self.shell.special = special + return except Exception: return "matplotlib" - exec("from pylab import *", self.shell.user_ns) - self.shell.special = special - return if special == "sympy": try: - import sympy + import sympy + sympy_init = "\n".join([ + "from sympy import *", + "x, y, z, t = symbols('x y z t')", + "k, m, n = symbols('k m n', integer=True)", + "f, g, h = symbols('f g h', cls=Function)", + "init_printing()", + ]) + exec(sympy_init, self.shell.user_ns) + self.shell.special = special + return except Exception: return "sympy" - sympy_init = "\n".join([ - "from sympy import *", - "x, y, z, t = symbols('x y z t')", - "k, m, n = symbols('k m n', integer=True)", - "f, g, h = symbols('f g h', cls=Function)", - "init_printing()", - ]) - exec(sympy_init, self.shell.user_ns) - self.shell.special = special - return if special == "cython": try: - import cython - - # Import pyximport to enable Cython files support for - # import statement - import pyximport - pyx_setup_args = {} - - # Add Numpy include dir to pyximport/distutils - try: - import numpy - pyx_setup_args['include_dirs'] = numpy.get_include() - except Exception: - pass - - # Setup pyximport and enable Cython files reload - pyximport.install(setup_args=pyx_setup_args, - reload_support=True) + import cython + + # Import pyximport to enable Cython files support for + # import statement + import pyximport + pyx_setup_args = {} + + # Add Numpy include dir to pyximport/distutils + try: + import numpy + pyx_setup_args['include_dirs'] = numpy.get_include() + except Exception: + pass + + # Setup pyximport and enable Cython files reload + pyximport.install(setup_args=pyx_setup_args, + reload_support=True) + + self.shell.run_line_magic("reload_ext", "Cython") + self.shell.special = special + return + except Exception: return "cython" - - self.shell.run_line_magic("reload_ext", "Cython") - self.shell.special = special - return raise NotImplementedError(f"{special}") @@ -983,11 +990,15 @@ def set_sympy_forecolor(self, background_color='dark'): pass # --- Others - def _load_autoreload_magic(self): + def _autoreload_magic(self, enable): """Load %autoreload magic.""" try: - self.shell.run_line_magic('reload_ext', 'autoreload') - self.shell.run_line_magic('autoreload', '2') + if enable: + self.shell.run_line_magic('reload_ext', 'autoreload') + self.shell.run_line_magic('autoreload', "2") + else: + self.shell.run_line_magic('autoreload', "off") + except Exception: pass diff --git a/spyder_kernels/console/shell.py b/spyder_kernels/console/shell.py index 3614370f..5256bb67 100644 --- a/spyder_kernels/console/shell.py +++ b/spyder_kernels/console/shell.py @@ -90,7 +90,9 @@ def enable_matplotlib(self, gui=None): gui, backend = super(SpyderShell, self).enable_matplotlib(gui) if self.update_gui_frontend: try: - self.kernel.frontend_call(blocking=False).update_matplotlib_gui(gui) + self.kernel.frontend_call( + blocking=False + ).update_matplotlib_gui(gui) except Exception: pass return gui, backend diff --git a/spyder_kernels/console/start.py b/spyder_kernels/console/start.py index 0502a134..eb910305 100644 --- a/spyder_kernels/console/start.py +++ b/spyder_kernels/console/start.py @@ -93,21 +93,6 @@ def kernel_config(): "del sys; del pdb" ) - # Run lines of code at startup - run_lines_o = os.environ.get('SPY_RUN_LINES_O') - if run_lines_o is not None: - spy_cfg.IPKernelApp.exec_lines += ( - [x.strip() for x in run_lines_o.split(';')] - ) - - # Load %autoreload magic - spy_cfg.IPKernelApp.exec_lines.append( - "get_ipython().kernel._load_autoreload_magic()") - - # Load wurlitzer extension - spy_cfg.IPKernelApp.exec_lines.append( - "get_ipython().kernel._load_wurlitzer()") - # Default inline backend configuration. # This is useful to have when people doesn't # use our config system to configure the @@ -131,16 +116,7 @@ def kernel_config(): } if is_module_installed('matplotlib'): - spy_cfg.IPKernelApp.exec_lines.append( - "get_ipython().kernel._set_mpl_backend('inline')" - ) - - # Run a file at startup - use_file_o = os.environ.get('SPY_USE_FILE_O') - run_file_o = os.environ.get('SPY_RUN_FILE_O') - if use_file_o == 'True' and run_file_o is not None: - if osp.exists(run_file_o): - spy_cfg.IPKernelApp.file_to_run = run_file_o + spy_cfg.IPKernelApp.matplotlib = "inline" # Autocall autocall_o = os.environ.get('SPY_AUTOCALL_O') diff --git a/spyder_kernels/customize/umr.py b/spyder_kernels/customize/umr.py index 57952f2b..dfa4688c 100644 --- a/spyder_kernels/customize/umr.py +++ b/spyder_kernels/customize/umr.py @@ -93,5 +93,5 @@ def run(self): modnames = modnames_to_reload print("\x1b[4;33m%s\x1b[24m%s\x1b[0m" % ("Reloaded modules", ": "+", ".join(modnames))) - + return modnames_to_reload From 0a07259c3d94226272b4a32ca4488f113e801a95 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 12 Nov 2023 17:17:04 +0100 Subject: [PATCH 22/27] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder_kernels/console/kernel.py | 5 +++++ spyder_kernels/customize/umr.py | 7 ++++--- spyder_kernels/utils/mpl.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index cd128f6d..81c04386 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -560,18 +560,22 @@ def set_matplotlib_conf(self, conf): conf.get(pylab_backend_n, inline_backend), pylab=conf.get(pylab_autoload_n, False) ) + if figure_format_n in conf: self._set_config_option( 'InlineBackend.figure_format', conf[figure_format_n] ) + if resolution_n in conf: self._set_mpl_inline_rc_config('figure.dpi', conf[resolution_n]) + if width_n in conf and height_n in conf: self._set_mpl_inline_rc_config( 'figure.figsize', (conf[width_n], conf[height_n]) ) + if bbox_inches_n in conf: self.set_mpl_inline_bbox_inches(conf[bbox_inches_n]) @@ -980,6 +984,7 @@ def set_sympy_forecolor(self, background_color='dark'): """Set SymPy forecolor depending on console background.""" if self.shell.special != "sympy": return + try: from sympy import init_printing if background_color == 'dark': diff --git a/spyder_kernels/customize/umr.py b/spyder_kernels/customize/umr.py index dfa4688c..e779ec33 100644 --- a/spyder_kernels/customize/umr.py +++ b/spyder_kernels/customize/umr.py @@ -59,9 +59,10 @@ def __init__(self, namelist=None, pathlist=None): def is_module_reloadable(self, module, modname): """Decide if a module is reloadable or not.""" - if (path_is_library(getattr(module, '__file__', None), - self.pathlist) or - self.is_module_in_namelist(modname)): + if ( + path_is_library(getattr(module, '__file__', None), self.pathlist) + or self.is_module_in_namelist(modname) + ): return False else: return True diff --git a/spyder_kernels/utils/mpl.py b/spyder_kernels/utils/mpl.py index 7756fd39..f5ab6988 100644 --- a/spyder_kernels/utils/mpl.py +++ b/spyder_kernels/utils/mpl.py @@ -22,7 +22,7 @@ MPL_BACKENDS_TO_SPYDER = { inline_backend: "inline", 'Qt5Agg': 'qt5', - 'QtAgg': 'qt5', # For Matplotlib 3.5+ + 'QtAgg': 'qt', # For Matplotlib 3.5+ 'TkAgg': 'tk', 'MacOSX': 'osx', } From c7f38bbe7e1b04d0c3c0d13f28a771753548c1d4 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 12 Nov 2023 17:31:45 +0100 Subject: [PATCH 23/27] qt5 to qt --- spyder_kernels/console/tests/test_console_kernel.py | 2 +- spyder_kernels/utils/mpl.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spyder_kernels/console/tests/test_console_kernel.py b/spyder_kernels/console/tests/test_console_kernel.py index b7f17cce..a9564feb 100644 --- a/spyder_kernels/console/tests/test_console_kernel.py +++ b/spyder_kernels/console/tests/test_console_kernel.py @@ -1137,7 +1137,7 @@ def test_locals_globals_in_pdb(kernel): @flaky(max_runs=3) -@pytest.mark.parametrize("backend", [None, 'inline', 'tk', 'qt5']) +@pytest.mark.parametrize("backend", [None, 'inline', 'tk', 'qt']) @pytest.mark.skipif( os.environ.get('USE_CONDA') != 'true', reason="Doesn't work with pip packages") diff --git a/spyder_kernels/utils/mpl.py b/spyder_kernels/utils/mpl.py index f5ab6988..00f47bac 100644 --- a/spyder_kernels/utils/mpl.py +++ b/spyder_kernels/utils/mpl.py @@ -21,7 +21,7 @@ # Mapping of matlotlib backends options to Spyder MPL_BACKENDS_TO_SPYDER = { inline_backend: "inline", - 'Qt5Agg': 'qt5', + 'Qt5Agg': 'qt', 'QtAgg': 'qt', # For Matplotlib 3.5+ 'TkAgg': 'tk', 'MacOSX': 'osx', @@ -31,7 +31,7 @@ def automatic_backend(): """Get Matplolib automatic backend option.""" if is_module_installed('PyQt5'): - auto_backend = 'qt5' + auto_backend = 'qt' elif is_module_installed('_tkinter'): auto_backend = 'tk' else: From 8297ac0d6362c13cfbc47a428e4a554b2bd4f299 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 13 Nov 2023 05:54:44 +0100 Subject: [PATCH 24/27] safe exec --- 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 81c04386..6bd7faab 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -150,6 +150,11 @@ def enable_faulthandler(self): faulthandler.enable(self.faulthandler_handle) return self.faulthandler_handle.name, main_id, system_ids + @comm_handler + def safe_exec(self, filename): + """Exec file using ipykernelapp _exec_file""" + self.parent._exec_file(filename) + @comm_handler def get_fault_text(self, fault_filename, main_id, ignore_ids): """Get fault text from old run.""" From b7d8bdc91437a7c0e3743a4e002e16531ba2e542 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 13 Nov 2023 05:59:28 +0100 Subject: [PATCH 25/27] move error --- spyder_kernels/console/kernel.py | 80 +++++++++++++++----------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index 6bd7faab..64c2656d 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -643,7 +643,10 @@ def set_configuration(self, conf): if value: ret[key] = self.enable_faulthandler() elif key == "special_kernel": - ret[key] = self.set_special_kernel(value) + try: + self.set_special_kernel(value) + except Exception: + ret["special_kernel_error"] = value elif key == "color scheme": self.set_color_scheme(value) elif key == "jedi_completer": @@ -707,56 +710,47 @@ def set_special_kernel(self, special): return if special == "pylab": - try: - import matplotlib - exec("from pylab import *", self.shell.user_ns) - self.shell.special = special - return - except Exception: - return "matplotlib" + import matplotlib + exec("from pylab import *", self.shell.user_ns) + self.shell.special = special + return + if special == "sympy": - try: - import sympy - sympy_init = "\n".join([ - "from sympy import *", - "x, y, z, t = symbols('x y z t')", - "k, m, n = symbols('k m n', integer=True)", - "f, g, h = symbols('f g h', cls=Function)", - "init_printing()", - ]) - exec(sympy_init, self.shell.user_ns) - self.shell.special = special - return - except Exception: - return "sympy" + import sympy + sympy_init = "\n".join([ + "from sympy import *", + "x, y, z, t = symbols('x y z t')", + "k, m, n = symbols('k m n', integer=True)", + "f, g, h = symbols('f g h', cls=Function)", + "init_printing()", + ]) + exec(sympy_init, self.shell.user_ns) + self.shell.special = special + return if special == "cython": - try: - import cython + import cython - # Import pyximport to enable Cython files support for - # import statement - import pyximport - pyx_setup_args = {} + # Import pyximport to enable Cython files support for + # import statement + import pyximport + pyx_setup_args = {} - # Add Numpy include dir to pyximport/distutils - try: - import numpy - pyx_setup_args['include_dirs'] = numpy.get_include() - except Exception: - pass - - # Setup pyximport and enable Cython files reload - pyximport.install(setup_args=pyx_setup_args, - reload_support=True) + # Add Numpy include dir to pyximport/distutils + try: + import numpy + pyx_setup_args['include_dirs'] = numpy.get_include() + except Exception: + pass - self.shell.run_line_magic("reload_ext", "Cython") - self.shell.special = special - return + # Setup pyximport and enable Cython files reload + pyximport.install(setup_args=pyx_setup_args, + reload_support=True) - except Exception: - return "cython" + self.shell.run_line_magic("reload_ext", "Cython") + self.shell.special = special + return raise NotImplementedError(f"{special}") From 4e6040fdafa29064ae5b45e715e93449cb4b3ed2 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 13 Nov 2023 06:37:30 +0100 Subject: [PATCH 26/27] fix test --- spyder_kernels/customize/tests/test_umr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder_kernels/customize/tests/test_umr.py b/spyder_kernels/customize/tests/test_umr.py index c237ccc7..fe84791b 100644 --- a/spyder_kernels/customize/tests/test_umr.py +++ b/spyder_kernels/customize/tests/test_umr.py @@ -51,11 +51,11 @@ def test_umr_run(user_module): umr = UserModuleReloader() from foo1.bar import square - assert umr.run() == ['foo', 'foo.bar'] + assert umr.run() == ['foo1', 'foo1.bar'] def test_umr_previous_modules(user_module): - """Test that UMR's previos_modules is working as expected.""" + """Test that UMR's previous_modules is working as expected.""" # Create user module user_module('foo2') From 3a6be2d02b21d12e80a7b5cdc3c30ab42bf4ecde Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 15 Nov 2023 07:37:22 +0100 Subject: [PATCH 27/27] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder_kernels/console/kernel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index 64c2656d..0585077a 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -152,7 +152,7 @@ def enable_faulthandler(self): @comm_handler def safe_exec(self, filename): - """Exec file using ipykernelapp _exec_file""" + """Safely execute a file using IPKernelApp._exec_file.""" self.parent._exec_file(filename) @comm_handler @@ -715,7 +715,6 @@ def set_special_kernel(self, special): self.shell.special = special return - if special == "sympy": import sympy sympy_init = "\n".join([