# -*- coding: utf-8 -*- # # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Widget that handle communications between the IPython Console and the Variable Explorer """ import logging import time try: time.monotonic # time.monotonic new in 3.3 except AttributeError: time.monotonic = time.time from pickle import PicklingError, UnpicklingError from qtpy.QtWidgets import QMessageBox from qtconsole.rich_jupyter_widget import RichJupyterWidget from spyder.config.base import _ from spyder.py3compat import PY2, to_text_string, TimeoutError from spyder_kernels.comms.commbase import CommError logger = logging.getLogger(__name__) # Max time before giving up when making a blocking call to the kernel CALL_KERNEL_TIMEOUT = 30 class NamepaceBrowserWidget(RichJupyterWidget): """ Widget with the necessary attributes and methods to handle communications between the IPython Console and the Variable Explorer """ # Reference to the nsb widget connected to this client namespacebrowser = None # To save the replies of kernel method executions (except # getting values of variables) _kernel_methods = {} # To save values and messages returned by the kernel _kernel_is_starting = True # --- Public API -------------------------------------------------- def set_namespacebrowser(self, namespacebrowser): """Set namespace browser widget""" self.namespacebrowser = namespacebrowser def refresh_namespacebrowser(self, interrupt=True): """Refresh namespace browser""" if self.kernel_client is None: return if self.namespacebrowser: self.call_kernel( interrupt=interrupt, callback=self.set_namespace_view ).get_namespace_view() self.call_kernel( interrupt=interrupt, callback=self.set_var_properties ).get_var_properties() def set_namespace_view(self, view): """Set the current namespace view.""" if self.namespacebrowser is not None: self.namespacebrowser.process_remote_view(view) def set_var_properties(self, properties): """Set var properties.""" if self.namespacebrowser is not None: self.namespacebrowser.set_var_properties(properties) def set_namespace_view_settings(self): """Set the namespace view settings""" if self.kernel_client is None: return if self.namespacebrowser: settings = self.namespacebrowser.get_view_settings() self.call_kernel( interrupt=True ).set_namespace_view_settings(settings) def get_value(self, name): """Ask kernel for a value""" reason_big = _("The variable is too big to be retrieved") reason_not_picklable = _("The variable is not picklable") reason_dead = _("The kernel is dead") reason_other = _("An error occured, see the console.") reason_comm = _("The comm channel is not working.") msg = _("%s.

" "Note: Please don't report this problem on Github, " "there's nothing to do about it.") try: return self.call_kernel( blocking=True, display_error=True, timeout=CALL_KERNEL_TIMEOUT).get_value(name) except TimeoutError: raise ValueError(msg % reason_big) except (PicklingError, UnpicklingError, TypeError): raise ValueError(msg % reason_not_picklable) except RuntimeError: raise ValueError(msg % reason_dead) except KeyError: raise except CommError: raise ValueError(msg % reason_comm) except Exception: raise ValueError(msg % reason_other) def set_value(self, name, value): """Set value for a variable""" self.call_kernel( interrupt=True, blocking=False, display_error=True, ).set_value(name, value) def remove_value(self, name): """Remove a variable""" self.call_kernel( interrupt=True, blocking=False, display_error=True, ).remove_value(name) def copy_value(self, orig_name, new_name): """Copy a variable""" self.call_kernel( interrupt=True, blocking=False, display_error=True, ).copy_value(orig_name, new_name) def load_data(self, filename, ext): """Load data from a file.""" overwrite = False if self.namespacebrowser.editor.var_properties: message = _('Do you want to overwrite old ' 'variables (if any) in the namespace ' 'when loading the data?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question( self, _('Data loading'), message, buttons) overwrite = result == QMessageBox.Yes try: return self.call_kernel( blocking=True, display_error=True, timeout=CALL_KERNEL_TIMEOUT).load_data( filename, ext, overwrite=overwrite) except ImportError as msg: module = str(msg).split("'")[1] msg = _("Spyder is unable to open the file " "you're trying to load because {module} is " "not installed. Please install " "this package in your working environment." "
").format(module=module) return msg except TimeoutError: msg = _("Data is too big to be loaded") return msg except (UnpicklingError, RuntimeError, CommError): return None def save_namespace(self, filename): try: return self.call_kernel( blocking=True, display_error=True, timeout=CALL_KERNEL_TIMEOUT).save_namespace(filename) except TimeoutError: msg = _("Data is too big to be saved") return msg except (UnpicklingError, RuntimeError, CommError): return None # ---- Private API (overrode by us) ---------------------------- def _handle_execute_reply(self, msg): """ Reimplemented to handle communications between Spyder and the kernel """ msg_id = msg['parent_header']['msg_id'] info = self._request_info['execute'].get(msg_id) # unset reading flag, because if execute finished, raw_input can't # still be pending. self._reading = False # Refresh namespacebrowser after the kernel starts running exec_count = msg['content'].get('execution_count', '') if exec_count == 0 and self._kernel_is_starting: if self.namespacebrowser is not None: self.set_namespace_view_settings() self.refresh_namespacebrowser(interrupt=False) self._kernel_is_starting = False self.ipyclient.t0 = time.monotonic() # Handle silent execution of kernel methods if info and info.kind == 'silent_exec_method' and not self._hidden: self.handle_exec_method(msg) self._request_info['execute'].pop(msg_id) else: super(NamepaceBrowserWidget, self)._handle_execute_reply(msg) def _handle_status(self, msg): """ Reimplemented to refresh the namespacebrowser after kernel restarts """ state = msg['content'].get('execution_state', '') msg_type = msg['parent_header'].get('msg_type', '') if state == 'starting': # This is needed to show the time a kernel # has been alive in each console. self.ipyclient.t0 = time.monotonic() self.ipyclient.timer.timeout.connect(self.ipyclient.show_time) self.ipyclient.timer.start(1000) # This handles restarts when the kernel dies # unexpectedly if not self._kernel_is_starting: self._kernel_is_starting = True elif state == 'idle' and msg_type == 'shutdown_request': # This handles restarts asked by the user if self.namespacebrowser is not None: self.set_namespace_view_settings() self.refresh_namespacebrowser(interrupt=False) self.ipyclient.t0 = time.monotonic() else: super(NamepaceBrowserWidget, self)._handle_status(msg)