Skip to content

Commit

Permalink
Merge pull request #3675 from ccordoba12/varexp-fixes
Browse files Browse the repository at this point in the history
PR: Several fixes for the Variable Explorer
  • Loading branch information
ccordoba12 authored Dec 28, 2016
2 parents 3a78b26 + a540f41 commit 3f82769
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 111 deletions.
7 changes: 3 additions & 4 deletions spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,8 @@
'exclude_capitalized': False,
'exclude_unsupported': True,
'truncate': True,
'minmax': False,
'remote_editing': False,
}),
'minmax': False
}),
('editor',
{
'printer_header/font/family': SANS_SERIF,
Expand Down Expand Up @@ -657,7 +656,7 @@
# or if you want to *rename* options, then you need to do a MAJOR update in
# version, e.g. from 3.0.0 to 4.0.0
# 3. You don't need to touch this value if you're just adding a new option
CONF_VERSION = '30.0.0'
CONF_VERSION = '31.0.0'

# Main configuration instance
try:
Expand Down
16 changes: 3 additions & 13 deletions spyder/plugins/variableexplorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from spyder.config.base import _
from spyder.plugins import SpyderPluginMixin
from spyder.plugins.configdialog import PluginConfigPage
from spyder.utils import programs
from spyder.utils import icon_manager as ima
from spyder.widgets.variableexplorer.namespacebrowser import NamespaceBrowser
from spyder.widgets.variableexplorer.utils import REMOTE_SETTINGS
Expand All @@ -40,24 +39,15 @@ def setup_page(self):
for option, text in filter_data]

display_group = QGroupBox(_("Display"))
display_data = []
if programs.is_module_installed('numpy'):
display_data.append(('minmax', _("Show arrays min/max"), ''))
display_data.append(
('remote_editing', _("Edit data in the remote process"),
_("Editors are opened in the remote process for NumPy "
"arrays, PIL images, lists, tuples and dictionaries.\n"
"This avoids transfering large amount of data between "
"the remote process and Spyder (through the socket)."))
)
display_data = [('minmax', _("Show arrays min/max"), '')]
display_boxes = [self.create_checkbox(text, option, tip=tip)
for option, text, tip in display_data]

ar_layout = QVBoxLayout()
ar_layout.addWidget(ar_box)
ar_layout.addWidget(ar_spin)
ar_group.setLayout(ar_layout)

filter_layout = QVBoxLayout()
for box in filter_boxes:
filter_layout.addWidget(box)
Expand Down
12 changes: 8 additions & 4 deletions spyder/utils/ipython/spyder_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
ipykernel.pickleutil.can_map.pop('numpy.ndarray')


# Excluded variables from the Variable Explorer (i.e. they are not
# shown at all there)
EXCLUDED_NAMES = ['In', 'Out', 'exit', 'get_ipython', 'quit']


class SpyderKernel(IPythonKernel):
"""Spyder kernel for Jupyter"""

Expand Down Expand Up @@ -96,8 +101,7 @@ def get_namespace_view(self):
settings = self.namespace_view_settings
if settings:
ns = self._get_current_namespace()
more_excluded_names = ['In', 'Out']
view = make_remote_view(ns, settings, more_excluded_names)
view = make_remote_view(ns, settings, EXCLUDED_NAMES)
return view

def get_var_properties(self):
Expand All @@ -109,7 +113,7 @@ def get_var_properties(self):
if settings:
ns = self._get_current_namespace()
data = get_remote_data(ns, settings, mode='editable',
more_excluded_names=['In', 'Out'])
more_excluded_names=EXCLUDED_NAMES)

properties = {}
for name, value in list(data.items()):
Expand Down Expand Up @@ -180,7 +184,7 @@ def save_namespace(self, filename):
ns = self._get_current_namespace()
settings = self.namespace_view_settings
data = get_remote_data(ns, settings, mode='picklable',
more_excluded_names=['In', 'Out']).copy()
more_excluded_names=EXCLUDED_NAMES).copy()
return iofunctions.save(data, filename)

# --- For Pdb
Expand Down
2 changes: 1 addition & 1 deletion spyder/widgets/ipythonconsole/namespacebrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def _handle_execute_reply(self, msg):
self._reading = False

# Refresh namespacebrowser after the kernel starts running
exec_count = msg['content']['execution_count']
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()
Expand Down
9 changes: 4 additions & 5 deletions spyder/widgets/variableexplorer/arrayeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def __init__(self, data, format="%.3f", xlabels=None, ylabels=None,
self.hue0 = huerange[0]
self.dhue = huerange[1]-huerange[0]
self.bgcolor_enabled = True
except TypeError:
except (TypeError, ValueError):
self.vmin = None
self.vmax = None
self.hue0 = None
Expand Down Expand Up @@ -606,11 +606,10 @@ def setup_and_check(self, data, title='', readonly=False,
self.data.flags.writeable = True
is_record_array = data.dtype.names is not None
is_masked_array = isinstance(data, np.ma.MaskedArray)
if data.size == 0:
self.error(_("Array is empty"))
return False

if data.ndim > 3:
self.error(_("Arrays with more than 3 dimensions are not supported"))
self.error(_("Arrays with more than 3 dimensions are not "
"supported"))
return False
if xlabels is not None and len(xlabels) != self.data.shape[1]:
self.error(_("The 'xlabels' argument length do no match array "
Expand Down
89 changes: 35 additions & 54 deletions spyder/widgets/variableexplorer/collectionseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
array, DataFrame, display_to_value, FakeObject, get_color_name,
get_human_readable_type, get_size, Image, is_editable_type, is_known_type,
MaskedArray, ndarray, np_savetxt, Series, sort_against, try_to_eval,
unsorted_unique, value_to_display,)
unsorted_unique, value_to_display, get_object_attrs, get_type_string)

if ndarray is not FakeObject:
from spyder.widgets.variableexplorer.arrayeditor import ArrayEditor
Expand All @@ -62,18 +62,26 @@


class ProxyObject(object):
"""Dictionary proxy to an unknown object"""
"""Dictionary proxy to an unknown object."""

def __init__(self, obj):
"""Constructor."""
self.__obj__ = obj

def __len__(self):
return len(dir(self.__obj__))
"""Get len according to detected attributes."""
return len(get_object_attrs(self.__obj__))

def __getitem__(self, key):
"""Get attribute corresponding to key."""
return getattr(self.__obj__, key)

def __setitem__(self, key, value):
setattr(self.__obj__, key, value)
"""Set attribute corresponding to key with value."""
try:
setattr(self.__obj__, key, value)
except TypeError:
pass


class ReadOnlyCollectionsModel(QAbstractTableModel):
Expand Down Expand Up @@ -108,6 +116,7 @@ def get_data(self):
def set_data(self, data, coll_filter=None):
"""Set model data"""
self._data = data
data_type = get_type_string(data)

if coll_filter is not None and not self.remote and \
isinstance(data, (tuple, list, dict)):
Expand All @@ -129,13 +138,16 @@ def set_data(self, data, coll_filter=None):
if not self.names:
self.header0 = _("Key")
else:
self.keys = dir(data)
self.keys = get_object_attrs(data)
self._data = data = self.showndata = ProxyObject(data)
self.title += _("Object")
if not self.names:
self.header0 = _("Attribute")

self.title += ' ('+str(len(self.keys))+' '+ _("elements")+')'
if not isinstance(self._data, ProxyObject):
self.title += (' (' + str(len(self.keys)) + ' ' +
_("elements") + ')')
else:
self.title += data_type

self.total_rows = len(self.keys)
if self.total_rows > LARGE_NROWS:
Expand Down Expand Up @@ -414,6 +426,8 @@ def createEditor(self, parent, option, index):
return None
try:
value = self.get_value(index)
if value is None:
return None
except Exception as msg:
QMessageBox.critical(self.parent(), _("Error"),
_("Spyder was unable to retrieve the value of "
Expand All @@ -436,8 +450,6 @@ def createEditor(self, parent, option, index):
#---editor = ArrayEditor
elif isinstance(value, (ndarray, MaskedArray)) \
and ndarray is not FakeObject:
if value.size == 0:
return None
editor = ArrayEditor(parent)
if not editor.setup_and_check(value, title=key, readonly=readonly):
return
Expand All @@ -448,8 +460,6 @@ def createEditor(self, parent, option, index):
elif isinstance(value, Image) and ndarray is not FakeObject \
and Image is not FakeObject:
arr = array(value)
if arr.size == 0:
return None
editor = ArrayEditor(parent)
if not editor.setup_and_check(arr, title=key, readonly=readonly):
return
Expand Down Expand Up @@ -482,7 +492,7 @@ def createEditor(self, parent, option, index):
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
return editor
#---editor = TextEditor
elif is_text_string(value) and len(value)>40:
elif is_text_string(value) and len(value) > 40:
editor = TextEditor(value, key)
self.create_dialog(editor, dict(model=index.model(), editor=editor,
key=key, readonly=readonly))
Expand Down Expand Up @@ -1277,8 +1287,9 @@ def __init__(self, parent=None):
self.data_copy = None
self.widget = None

def setup(self, data, title='', readonly=False, width=500, remote=False,
def setup(self, data, title='', readonly=False, width=650, remote=False,
icon=None, parent=None):
"""Setup editor."""
if isinstance(data, dict):
# dictionnary
self.data_copy = data.copy()
Expand All @@ -1291,7 +1302,7 @@ def setup(self, data, title='', readonly=False, width=500, remote=False,
# unknown object
import copy
self.data_copy = copy.deepcopy(data)
datalen = len(dir(data))
datalen = len(get_object_attrs(data))
self.widget = CollectionsEditorWidget(self, self.data_copy, title=title,
readonly=readonly, remote=remote)

Expand All @@ -1311,8 +1322,8 @@ def setup(self, data, title='', readonly=False, width=500, remote=False,

constant = 121
row_height = 30
error_margin = 20
height = constant + row_height*min([15, datalen]) + error_margin
error_margin = 10
height = constant + row_height * min([10, datalen]) + error_margin
self.resize(width, height)

self.setWindowTitle(self.widget.get_title())
Expand Down Expand Up @@ -1364,13 +1375,11 @@ def __init__(self, parent, data, minmax=False,
copy_value_func=None, is_list_func=None, get_len_func=None,
is_array_func=None, is_image_func=None, is_dict_func=None,
get_array_shape_func=None, get_array_ndim_func=None,
oedit_func=None, plot_func=None, imshow_func=None,
plot_func=None, imshow_func=None,
is_data_frame_func=None, is_series_func=None,
show_image_func=None, remote_editing=False):
show_image_func=None):
BaseTableView.__init__(self, parent)

self.remote_editing_enabled = None

self.remove_values = remove_values_func
self.copy_value = copy_value_func
self.new_value = new_value_func
Expand All @@ -1384,7 +1393,6 @@ def __init__(self, parent, data, minmax=False,
self.is_dict = is_dict_func
self.get_array_shape = get_array_shape_func
self.get_array_ndim = get_array_ndim_func
self.oedit = oedit_func
self.plot = plot_func
self.imshow = imshow_func
self.show_image = show_image_func
Expand All @@ -1410,53 +1418,26 @@ def setup_menu(self, minmax):
menu = BaseTableView.setup_menu(self, minmax)
return menu

def oedit_possible(self, key):
if (self.is_list(key) or self.is_dict(key)
or self.is_array(key) or self.is_image(key)
or self.is_data_frame(key) or self.is_series(key)):
# If this is a remote dict editor, the following avoid
# transfering large amount of data through the socket
return True

def edit_item(self):
"""
Reimplement BaseTableView's method to edit item
Some supported data types are directly edited in the remote process,
thus avoiding to transfer large amount of data through the socket from
the remote process to Spyder
"""
if self.remote_editing_enabled:
index = self.currentIndex()
if not index.isValid():
return
key = self.model.get_key(index)
if self.oedit_possible(key):
# If this is a remote dict editor, the following avoid
# transfering large amount of data through the socket
self.oedit(key)
else:
BaseTableView.edit_item(self)
else:
BaseTableView.edit_item(self)


#==============================================================================
# =============================================================================
# Tests
#==============================================================================
# =============================================================================
def get_test_data():
"""Create test data"""
"""Create test data."""
import numpy as np
from spyder.pil_patch import Image
image = Image.fromarray(np.random.random_integers(255, size=(100, 100)),
mode='P')
testdict = {'d': 1, 'a': np.random.rand(10, 10), 'b': [1, 2]}
testdate = datetime.date(1945, 5, 8)

class Foobar(object):

def __init__(self):
self.text = "toto"
self.testdict = testdict
self.testdate = testdate

foobar = Foobar()
return {'object': foobar,
'str': 'kjkj kj k j j kj k jkj',
Expand Down
Loading

0 comments on commit 3f82769

Please sign in to comment.