# -*- coding: utf-8 -*- # # Copyright © 2009-2010 Pierre Raybaut # Licensed under the terms of the MIT License # (see spyderlib/__init__.py for details) """Namespace browser widget""" import os.path as osp import socket from spyderlib.qt.QtGui import (QWidget, QVBoxLayout, QHBoxLayout, QMenu, QToolButton, QMessageBox, QApplication, QCursor, QInputDialog) from spyderlib.qt.QtCore import SIGNAL, Qt, Signal from spyderlib.qt.compat import getopenfilenames, getsavefilename # Local imports from spyderlib.widgets.externalshell.monitor import ( monitor_set_global, monitor_get_global, monitor_del_global, monitor_copy_global, monitor_save_globals, monitor_load_globals, communicate, REMOTE_SETTINGS) from spyderlib.widgets.dicteditor import (RemoteDictEditorTableView, DictEditorTableView) from spyderlib.widgets.dicteditorutils import globalsfilter from spyderlib.utils import encoding from spyderlib.utils.misc import fix_reference_name from spyderlib.utils.programs import is_module_installed from spyderlib.utils.qthelpers import (get_icon, create_toolbutton, add_actions, create_action) from spyderlib.utils.iofuncs import iofunctions from spyderlib.widgets.importwizard import ImportWizard from spyderlib.baseconfig import _, get_supported_types from spyderlib.py3compat import is_text_string, to_text_string, getcwd SUPPORTED_TYPES = get_supported_types() class NamespaceBrowser(QWidget): """Namespace browser (global variables explorer widget)""" sig_option_changed = Signal(str, object) def __init__(self, parent): QWidget.__init__(self, parent) self.shellwidget = None self.is_internal_shell = None self.ipyclient = None self.is_ipykernel = None self.is_visible = True # Do not modify: light mode won't work! self.setup_in_progress = None # Remote dict editor settings self.check_all = None self.exclude_private = None self.exclude_uppercase = None self.exclude_capitalized = None self.exclude_unsupported = None self.excluded_names = None self.truncate = None self.minmax = None self.remote_editing = None self.autorefresh = None self.editor = None self.exclude_private_action = None self.exclude_uppercase_action = None self.exclude_capitalized_action = None self.exclude_unsupported_action = None self.filename = None def setup(self, check_all=None, exclude_private=None, exclude_uppercase=None, exclude_capitalized=None, exclude_unsupported=None, excluded_names=None, truncate=None, minmax=None, remote_editing=None, autorefresh=None): """Setup the namespace browser""" assert self.shellwidget is not None self.check_all = check_all self.exclude_private = exclude_private self.exclude_uppercase = exclude_uppercase self.exclude_capitalized = exclude_capitalized self.exclude_unsupported = exclude_unsupported self.excluded_names = excluded_names self.truncate = truncate self.minmax = minmax self.remote_editing = remote_editing self.autorefresh = autorefresh if self.editor is not None: self.editor.setup_menu(truncate, minmax) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action.setChecked(exclude_unsupported) # Don't turn autorefresh on for IPython kernels # See Issue 1450 if not self.is_ipykernel: self.auto_refresh_button.setChecked(autorefresh) self.refresh_table() return # Dict editor: if self.is_internal_shell: self.editor = DictEditorTableView(self, None, truncate=truncate, minmax=minmax) else: self.editor = RemoteDictEditorTableView(self, None, truncate=truncate, minmax=minmax, remote_editing=remote_editing, get_value_func=self.get_value, set_value_func=self.set_value, new_value_func=self.set_value, remove_values_func=self.remove_values, copy_value_func=self.copy_value, is_list_func=self.is_list, get_len_func=self.get_len, is_array_func=self.is_array, is_image_func=self.is_image, is_dict_func=self.is_dict, is_data_frame_func=self.is_data_frame, is_series_func=self.is_series, get_array_shape_func=self.get_array_shape, get_array_ndim_func=self.get_array_ndim, oedit_func=self.oedit, plot_func=self.plot, imshow_func=self.imshow, show_image_func=self.show_image) self.editor.sig_option_changed.connect(self.sig_option_changed.emit) self.editor.sig_files_dropped.connect(self.import_data) # Setup layout hlayout = QHBoxLayout() vlayout = QVBoxLayout() toolbar = self.setup_toolbar(exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported, autorefresh) vlayout.setAlignment(Qt.AlignTop) for widget in toolbar: vlayout.addWidget(widget) hlayout.addWidget(self.editor) hlayout.addLayout(vlayout) self.setLayout(hlayout) hlayout.setContentsMargins(0, 0, 0, 0) self.sig_option_changed.connect(self.option_changed) def set_shellwidget(self, shellwidget): """Bind shellwidget instance to namespace browser""" self.shellwidget = shellwidget from spyderlib.widgets import internalshell self.is_internal_shell = isinstance(self.shellwidget, internalshell.InternalShell) self.is_ipykernel = self.shellwidget.is_ipykernel if not self.is_internal_shell: shellwidget.set_namespacebrowser(self) def set_ipyclient(self, ipyclient): """Bind ipyclient instance to namespace browser""" self.ipyclient = ipyclient def setup_toolbar(self, exclude_private, exclude_uppercase, exclude_capitalized, exclude_unsupported, autorefresh): """Setup toolbar""" self.setup_in_progress = True toolbar = [] refresh_button = create_toolbutton(self, text=_("Refresh"), icon=get_icon('reload.png'), triggered=self.refresh_table) self.auto_refresh_button = create_toolbutton(self, text=_("Refresh periodically"), icon=get_icon('auto_reload.png'), toggled=self.toggle_auto_refresh) self.auto_refresh_button.setChecked(autorefresh) load_button = create_toolbutton(self, text=_("Import data"), icon=get_icon('fileimport.png'), triggered=self.import_data) self.save_button = create_toolbutton(self, text=_("Save data"), icon=get_icon('filesave.png'), triggered=lambda: self.save_data(self.filename)) self.save_button.setEnabled(False) save_as_button = create_toolbutton(self, text=_("Save data as..."), icon=get_icon('filesaveas.png'), triggered=self.save_data) toolbar += [refresh_button, self.auto_refresh_button, load_button, self.save_button, save_as_button] self.exclude_private_action = create_action(self, _("Exclude private references"), tip=_("Exclude references which name starts" " with an underscore"), toggled=lambda state: self.sig_option_changed.emit('exclude_private', state)) self.exclude_private_action.setChecked(exclude_private) self.exclude_uppercase_action = create_action(self, _("Exclude all-uppercase references"), tip=_("Exclude references which name is uppercase"), toggled=lambda state: self.sig_option_changed.emit('exclude_uppercase', state)) self.exclude_uppercase_action.setChecked(exclude_uppercase) self.exclude_capitalized_action = create_action(self, _("Exclude capitalized references"), tip=_("Exclude references which name starts with an " "uppercase character"), toggled=lambda state: self.sig_option_changed.emit('exclude_capitalized', state)) self.exclude_capitalized_action.setChecked(exclude_capitalized) self.exclude_unsupported_action = create_action(self, _("Exclude unsupported data types"), tip=_("Exclude references to unsupported data types" " (i.e. which won't be handled/saved correctly)"), toggled=lambda state: self.sig_option_changed.emit('exclude_unsupported', state)) self.exclude_unsupported_action.setChecked(exclude_unsupported) options_button = create_toolbutton(self, text=_("Options"), icon=get_icon('tooloptions.png')) toolbar.append(options_button) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) editor = self.editor actions = [self.exclude_private_action, self.exclude_uppercase_action, self.exclude_capitalized_action, self.exclude_unsupported_action, None, editor.truncate_action] if is_module_installed('numpy'): actions.append(editor.minmax_action) add_actions(menu, actions) options_button.setMenu(menu) self.setup_in_progress = False return toolbar def option_changed(self, option, value): """Option has changed""" setattr(self, to_text_string(option), value) if not self.is_internal_shell: settings = self.get_view_settings() communicate(self._get_sock(), 'set_remote_view_settings()', settings=[settings]) def visibility_changed(self, enable): """Notify the widget whether its container (the namespace browser plugin is visible or not""" # This is slowing down Spyder a lot if too much data is present in # the Variable Explorer, and users give focus to it after being hidden. # This also happens when the Variable Explorer is visible and users # give focus to Spyder after using another application (like Chrome # or Firefox). # That's why we've decided to remove this feature # Fixes Issue 2593 # # self.is_visible = enable # if enable: # self.refresh_table() pass def toggle_auto_refresh(self, state): """Toggle auto refresh state""" self.autorefresh = state if not self.setup_in_progress and not self.is_internal_shell: communicate(self._get_sock(), "set_monitor_auto_refresh(%r)" % state) def _get_sock(self): """Return socket connection""" return self.shellwidget.introspection_socket def get_internal_shell_filter(self, mode, check_all=None): """ Return internal shell data types filter: * check_all: check all elements data types for sequences (dict, list, tuple) * mode (string): 'editable' or 'picklable' """ assert mode in list(SUPPORTED_TYPES.keys()) if check_all is None: check_all = self.check_all def wsfilter(input_dict, check_all=check_all, filters=tuple(SUPPORTED_TYPES[mode])): """Keep only objects that can be pickled""" return globalsfilter( input_dict, check_all=check_all, filters=filters, exclude_private=self.exclude_private, exclude_uppercase=self.exclude_uppercase, exclude_capitalized=self.exclude_capitalized, exclude_unsupported=self.exclude_unsupported, excluded_names=self.excluded_names) return wsfilter def get_view_settings(self): """Return dict editor view settings""" settings = {} for name in REMOTE_SETTINGS: settings[name] = getattr(self, name) return settings def refresh_table(self): """Refresh variable table""" if self.is_visible and self.isVisible(): if self.is_internal_shell: # Internal shell wsfilter = self.get_internal_shell_filter('editable') self.editor.set_filter(wsfilter) interpreter = self.shellwidget.interpreter if interpreter is not None: self.editor.set_data(interpreter.namespace) self.editor.adjust_columns() elif self.shellwidget.is_running(): # import time; print >>STDOUT, time.ctime(time.time()), "Refreshing namespace browser" sock = self._get_sock() if sock is None: return try: communicate(sock, "refresh()") except socket.error: # Process was terminated before calling this method pass def process_remote_view(self, remote_view): """Process remote view""" if remote_view is not None: self.set_data(remote_view) #------ Remote Python process commands ------------------------------------ def get_value(self, name): value = monitor_get_global(self._get_sock(), name) if value is None: if communicate(self._get_sock(), '%s is not None' % name): import pickle msg = to_text_string(_("Object %s is not picklable") % name) raise pickle.PicklingError(msg) return value def set_value(self, name, value): monitor_set_global(self._get_sock(), name, value) self.refresh_table() def remove_values(self, names): for name in names: monitor_del_global(self._get_sock(), name) self.refresh_table() def copy_value(self, orig_name, new_name): monitor_copy_global(self._get_sock(), orig_name, new_name) self.refresh_table() def is_list(self, name): """Return True if variable is a list or a tuple""" return communicate(self._get_sock(), 'isinstance(%s, (tuple, list))' % name) def is_dict(self, name): """Return True if variable is a dictionary""" return communicate(self._get_sock(), 'isinstance(%s, dict)' % name) def get_len(self, name): """Return sequence length""" return communicate(self._get_sock(), "len(%s)" % name) def is_array(self, name): """Return True if variable is a NumPy array""" return communicate(self._get_sock(), 'is_array("%s")' % name) def is_image(self, name): """Return True if variable is a PIL.Image image""" return communicate(self._get_sock(), 'is_image("%s")' % name) def is_data_frame(self, name): """Return True if variable is a DataFrame""" return communicate(self._get_sock(), "isinstance(globals()['%s'], DataFrame)" % name) def is_series(self, name): """Return True if variable is a Series""" return communicate(self._get_sock(), "isinstance(globals()['%s'], Series)" % name) def get_array_shape(self, name): """Return array's shape""" return communicate(self._get_sock(), "%s.shape" % name) def get_array_ndim(self, name): """Return array's ndim""" return communicate(self._get_sock(), "%s.ndim" % name) def plot(self, name, funcname): command = "import spyderlib.pyplot; "\ "__fig__ = spyderlib.pyplot.figure(); "\ "__items__ = getattr(spyderlib.pyplot, '%s')(%s); "\ "spyderlib.pyplot.show(); "\ "del __fig__, __items__;" % (funcname, name) if self.is_ipykernel: self.ipyclient.shellwidget.execute("%%varexp --%s %s" % (funcname, name)) else: self.shellwidget.send_to_process(command) def imshow(self, name): command = "import spyderlib.pyplot; " \ "__fig__ = spyderlib.pyplot.figure(); " \ "__items__ = spyderlib.pyplot.imshow(%s); " \ "spyderlib.pyplot.show(); del __fig__, __items__;" % name if self.is_ipykernel: self.ipyclient.shellwidget.execute("%%varexp --imshow %s" % name) else: self.shellwidget.send_to_process(command) def show_image(self, name): command = "%s.show()" % name if self.is_ipykernel: self.ipyclient.shellwidget.execute(command) else: self.shellwidget.send_to_process(command) def oedit(self, name): command = "from spyderlib.widgets.objecteditor import oedit; " \ "oedit('%s', modal=False, namespace=locals());" % name self.shellwidget.send_to_process(command) #------ Set, load and save data ------------------------------------------- def set_data(self, data): """Set data""" if data != self.editor.model.get_data(): self.editor.set_data(data) self.editor.adjust_columns() def collapse(self): """Collapse""" self.emit(SIGNAL('collapse()')) def import_data(self, filenames=None): """Import data from text file""" title = _("Import data") if filenames is None: if self.filename is None: basedir = getcwd() else: basedir = osp.dirname(self.filename) filenames, _selfilter = getopenfilenames(self, title, basedir, iofunctions.load_filters) if not filenames: return elif is_text_string(filenames): filenames = [filenames] for filename in filenames: self.filename = to_text_string(filename) ext = osp.splitext(self.filename)[1].lower() if ext not in iofunctions.load_funcs: buttons = QMessageBox.Yes | QMessageBox.Cancel answer = QMessageBox.question(self, title, _("Unsupported file extension '%s'

" "Would you like to import it anyway " "(by selecting a known file format)?" ) % ext, buttons) if answer == QMessageBox.Cancel: return formats = list(iofunctions.load_extensions.keys()) item, ok = QInputDialog.getItem(self, title, _('Open file as:'), formats, 0, False) if ok: ext = iofunctions.load_extensions[to_text_string(item)] else: return load_func = iofunctions.load_funcs[ext] # 'import_wizard' (self.setup_io) if is_text_string(load_func): # Import data with import wizard error_message = None try: text, _encoding = encoding.read(self.filename) if self.is_internal_shell: self.editor.import_from_string(text) else: base_name = osp.basename(self.filename) editor = ImportWizard(self, text, title=base_name, varname=fix_reference_name(base_name)) if editor.exec_(): var_name, clip_data = editor.get_data() monitor_set_global(self._get_sock(), var_name, clip_data) except Exception as error: error_message = str(error) else: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() if self.is_internal_shell: namespace, error_message = load_func(self.filename) interpreter = self.shellwidget.interpreter for key in list(namespace.keys()): new_key = fix_reference_name(key, blacklist=list(interpreter.namespace.keys())) if new_key != key: namespace[new_key] = namespace.pop(key) if error_message is None: interpreter.namespace.update(namespace) else: error_message = monitor_load_globals(self._get_sock(), self.filename, ext) QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical(self, title, _("Unable to load '%s'" "

Error message:
%s" ) % (self.filename, error_message)) self.refresh_table() def save_data(self, filename=None): """Save data""" if filename is None: filename = self.filename if filename is None: filename = getcwd() filename, _selfilter = getsavefilename(self, _("Save data"), filename, iofunctions.save_filters) if filename: self.filename = filename else: return False QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() if self.is_internal_shell: wsfilter = self.get_internal_shell_filter('picklable', check_all=True) namespace = wsfilter(self.shellwidget.interpreter.namespace).copy() error_message = iofunctions.save(namespace, filename) else: settings = self.get_view_settings() error_message = monitor_save_globals(self._get_sock(), settings, filename) QApplication.restoreOverrideCursor() QApplication.processEvents() if error_message is not None: QMessageBox.critical(self, _("Save data"), _("Unable to save current workspace" "

Error message:
%s") % error_message) self.save_button.setEnabled(self.filename is not None)