Skip to content

Commit

Permalink
Merge from 3.x: PR #3675
Browse files Browse the repository at this point in the history
Fixes #3759
Fixes #3459

Conflicts:
- spyder/config/main.py
- spyder/plugins/variableexplorer.py
- spyder/widgets/variableexplorer/collectionseditor.py
- spyder/widgets/variableexplorer/namespacebrowser.py
- spyder/widgets/variableexplorer/utils.py
  • Loading branch information
ccordoba12 committed Dec 28, 2016
2 parents f23f919 + 3f82769 commit 10b37bb
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 100 deletions.
7 changes: 3 additions & 4 deletions spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,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 @@ -655,7 +654,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 = '32.0.0'
CONF_VERSION = '33.0.0'

# Main configuration instance
try:
Expand Down
12 changes: 1 addition & 11 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.api.plugins import SpyderPluginWidget
from spyder.api.preferences 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 @@ -33,16 +32,7 @@ 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]

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 @@ -174,7 +174,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
92 changes: 35 additions & 57 deletions spyder/widgets/variableexplorer/collectionseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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 @@ -73,18 +73,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 @@ -119,6 +127,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 @@ -140,13 +149,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 @@ -425,6 +437,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 @@ -447,8 +461,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 @@ -459,8 +471,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 @@ -493,7 +503,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 @@ -1288,8 +1298,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 @@ -1302,7 +1313,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 @@ -1322,8 +1333,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,7 +1375,6 @@ def __init__(self, parent, data, minmax=False, shellwidget=None,
remote_editing=False, dataframe_format=None):
BaseTableView.__init__(self, parent)

self.remote_editing_enabled = None
self.shellwidget = shellwidget
self.var_properties = {}

Expand Down Expand Up @@ -1459,65 +1469,33 @@ def show_image(self, name):
command = "%s.show()" % name
self.shellwidget.execute(command)

def oedit(self, name):
"""Edit item"""
command = "from spyder.widgets.variableexplorer.objecteditor import oedit; " \
"oedit('%s', modal=False, namespace=locals());" % name
self.shellwidget.send_to_process(command)
#--------------------------------------------------------------------------
# -------------------------------------------------------------------------

def setup_menu(self, minmax):
"""Setup context menu"""
"""Setup context menu."""
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 10b37bb

Please sign in to comment.