Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

PR: Simplify kernel configuration #466

Merged
merged 14 commits into from
Nov 15, 2023
6 changes: 4 additions & 2 deletions spyder_kernels/comms/frontendcomm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 1 addition & 4 deletions spyder_kernels/comms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
227 changes: 157 additions & 70 deletions spyder_kernels/console/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
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)
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
Expand Down Expand Up @@ -65,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

Expand All @@ -77,6 +76,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):
Expand Down Expand Up @@ -114,7 +125,6 @@ def publish_state(self):
except Exception:
pass

@comm_handler
def enable_faulthandler(self):
"""
Open a file to save the faulthandling and identifiers for
Expand Down Expand Up @@ -260,11 +270,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):
"""
Expand Down Expand Up @@ -529,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:
Expand All @@ -538,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:
impact27 marked this conversation as resolved.
Show resolved Hide resolved
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:
impact27 marked this conversation as resolved.
Show resolved Hide resolved
self._set_mpl_inline_rc_config(
'figure.figsize',
(conf[width_n], conf[height_n])
)
if bbox_inches_n in conf:
impact27 marked this conversation as resolved.
Show resolved Hide resolved
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):
"""
Expand Down Expand Up @@ -599,11 +617,45 @@ 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 == "special_kernel":
ret[key] = self.set_special_kernel(value)
elif key == "color scheme":
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":
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."""
Expand Down Expand Up @@ -631,28 +683,66 @@ def close_all_mpl_figures(self):
except:
pass

@comm_handler
def is_special_kernel_valid(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"
exec("from pylab import *", self.shell.user_ns)
self.shell.special = special
return

impact27 marked this conversation as resolved.
Show resolved Hide resolved
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()",
])
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)
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):
Expand Down Expand Up @@ -805,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
Expand Down Expand Up @@ -843,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):
"""
Expand Down Expand Up @@ -876,24 +969,18 @@ 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:
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:
impact27 marked this conversation as resolved.
Show resolved Hide resolved
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):
Expand Down
20 changes: 7 additions & 13 deletions spyder_kernels/console/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@
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
from spyder_kernels.comms.frontendcomm import CommError
from spyder_kernels.comms.decorators import comm_handler
from spyder_kernels.utils.mpl import automatic_backend

Expand All @@ -51,16 +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()

# Used for checking correct version by spyder
self._spyder_kernels_version = (
spyder_kernels.__version__,
sys.executable
)
self.update_gui_frontend = False

# register post_execute
self.events.register('post_execute', self.do_post_execute)
Expand Down Expand Up @@ -94,14 +88,14 @@ 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
@comm_handler
def set_pdb_configuration(self, pdb_conf):
"""
Set Pdb configuration.
Expand Down
Loading