Skip to content

Commit

Permalink
Merge pull request #8852 from dalthviz/fixes_issue_558
Browse files Browse the repository at this point in the history
PR: Add an Object Explorer to the Variable Explorer
  • Loading branch information
ccordoba12 authored Jun 24, 2019
2 parents 873353c + de751fe commit 9543582
Show file tree
Hide file tree
Showing 20 changed files with 2,216 additions and 44 deletions.
44 changes: 44 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,50 @@ Files covered:
spyder/plugins/variableexplorer/widgets/dataframeeditor.py


-------------------------------------------------------------------------------



Classes from objbrowser 1.2.1
-----------------------------


Copyright (c) 2016 Pepijn Kenter


Author: Pepijn Kenter | [email protected] | https://github.com/titusjan
Site/Source: https://github.com/titusjan/objbrowser
License: MIT (Expat) License | https://opensource.org/licenses/MIT

Modifications made to each class to adapt them to Spyder's needs.


objbrowser is distributed under the MIT license.


Use the core functionality of the project, mainly changing the code to use
the upstream version of qtpy project (since it is already a dependency of
Spyder).

See below for the full text of the MIT (Expat) license.

The current objbrowser license can be viewed at:
https://github.com/titusjan/objbrowser/blob/master/LICENSE.txt

The current version of the original files can be viewed at:
https://github.com/titusjan/objbrowser/tree/master/objbrowser


Files covered:

spyder/plugins/variableexplorer/widgets/objectexplorer/attribute_model.py
spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py
spyder/plugins/variableexplorer/widgets/objectexplorer/toggle_column_mixin.py
spyder/plugins/variableexplorer/widgets/objectexplorer/tree_item.py
spyder/plugins/variableexplorer/widgets/objectexplorerr/tree_model.py
spyder/plugins/variableexplorer/widgets/objectexplorer/utils.py


===============================================================================


Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ a Python version greater than 2.7 or 3.4 (Python <=3.3 is no longer supported).
* **Chardet**: Character encoding auto-detection in Python.
* **Numpydoc**: Used by Jedi to get function return types from Numpydocstrings.
* **Cloudpickle**: Serialize variables in the IPython kernel to send to Spyder.
* **spyder-kernels** 1.2.0+: Jupyter kernels for the Spyder console.
* **spyder-kernels** 1.4.0+: Jupyter kernels for the Spyder console.
* **keyring**: Save Github credentials to report errors securely.
* **QDarkStyle** 2.6.4+: A dark stylesheet for Qt applications, used for Spyder's dark theme.
* **atomicwrites**: Atomic file writes.
Expand Down
3 changes: 3 additions & 0 deletions continuous_integration/circle/modules_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ for f in spyder/*/*/*/*/*.py; do
if [[ $f == spyder/plugins/editor/lsp/*/*.py ]]; then
continue
fi
if [[ $f == spyder/plugins/variableexplorer/widgets/objectexplorer/__init__.py ]]; then
continue
fi
python "$f"
if [ $? -ne 0 ]; then
exit 1
Expand Down
2 changes: 1 addition & 1 deletion requirements/conda.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ chardet >=2.0.0
numpydoc
pyqt <5.10
keyring
spyder-kernels >=1.2
spyder-kernels >=1.4
python-language-server >=0.19
qdarkstyle >=2.6.4
atomicwrites
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def run(self):
'pyzmq',
'chardet>=2.0.0',
'numpydoc',
'spyder-kernels>=1.2',
'spyder-kernels>=1.4',
'qdarkstyle>=2.6.4',
'atomicwrites',
'diff-match-patch',
Expand Down
4 changes: 3 additions & 1 deletion spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@
'exclude_capitalized': False,
'exclude_unsupported': True,
'truncate': True,
'minmax': False
'minmax': False,
'show_callable_attributes': True,
'show_special_attributes': False
}),
('plots',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ def mock_get_option(self, option):

app = qapplication()
settings = VariableExplorer(None).get_settings()
expected = {'remote1': 'remote1val', 'remote2': 'remote2val',
'dataframe_format': '%3d'}
expected = {'dataframe_format': '%3d',
'remote1': 'remote1val',
'remote2': 'remote2val'}
assert settings == expected


Expand Down
110 changes: 85 additions & 25 deletions spyder/plugins/variableexplorer/widgets/collectionseditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
mimedata2url)
from spyder.plugins.variableexplorer.widgets.importwizard import ImportWizard
from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor
from spyder.plugins.variableexplorer.widgets.objectexplorer import (
ObjectExplorer)

if ndarray is not FakeObject:
from spyder.plugins.variableexplorer.widgets.arrayeditor import (
Expand Down Expand Up @@ -120,13 +122,18 @@ class ReadOnlyCollectionsModel(QAbstractTableModel):
sig_setting_data = Signal()

def __init__(self, parent, data, title="", names=False,
minmax=False, dataframe_format=None, remote=False):
minmax=False, dataframe_format=None,
show_callable_attributes=None,
show_special_attributes=None,
remote=False):
QAbstractTableModel.__init__(self, parent)
if data is None:
data = {}
self.names = names
self.minmax = minmax
self.dataframe_format = dataframe_format
self.show_callable_attributes = show_callable_attributes
self.show_special_attributes = show_special_attributes
self.remote = remote
self.header0 = None
self._data = None
Expand Down Expand Up @@ -455,7 +462,7 @@ def show_warning(self, index):
else:
return False

def createEditor(self, parent, option, index):
def createEditor(self, parent, option, index, object_explorer=False):
"""Overriding method createEditor"""
if index.column() < 3:
return None
Expand All @@ -482,25 +489,25 @@ def createEditor(self, parent, option, index):
readonly = (isinstance(value, (tuple, set)) or self.parent().readonly
or not is_known_type(value))
# CollectionsEditor for a list, tuple, dict, etc.
if isinstance(value, (list, set, tuple, dict)):
if isinstance(value, (list, set, tuple, dict)) and not object_explorer:
editor = CollectionsEditor(parent=parent)
editor.setup(value, key, icon=self.parent().windowIcon(),
readonly=readonly)
self.create_dialog(editor, dict(model=index.model(), editor=editor,
key=key, readonly=readonly))
return None
# ArrayEditor for a Numpy array
elif isinstance(value, (ndarray, MaskedArray)) \
and ndarray is not FakeObject:
elif (isinstance(value, (ndarray, MaskedArray)) and
ndarray is not FakeObject and not object_explorer):
editor = ArrayEditor(parent=parent)
if not editor.setup_and_check(value, title=key, readonly=readonly):
return
self.create_dialog(editor, dict(model=index.model(), editor=editor,
key=key, readonly=readonly))
return None
# ArrayEditor for an images
elif isinstance(value, Image) and ndarray is not FakeObject \
and Image is not FakeObject:
elif (isinstance(value, Image) and ndarray is not FakeObject and
Image is not FakeObject and not object_explorer):
arr = array(value)
editor = ArrayEditor(parent=parent)
if not editor.setup_and_check(arr, title=key, readonly=readonly):
Expand All @@ -511,8 +518,8 @@ def createEditor(self, parent, option, index):
conv=conv_func))
return None
# DataFrameEditor for a pandas dataframe, series or index
elif isinstance(value, (DataFrame, Index, Series)) \
and DataFrame is not FakeObject:
elif (isinstance(value, (DataFrame, Index, Series))
and DataFrame is not FakeObject and not object_explorer):
editor = DataFrameEditor(parent=parent)
if not editor.setup_and_check(value, title=key):
return
Expand All @@ -522,7 +529,7 @@ def createEditor(self, parent, option, index):
key=key, readonly=readonly))
return None
# QDateEdit and QDateTimeEdit for a dates or datetime respectively
elif isinstance(value, datetime.date):
elif isinstance(value, datetime.date) and not object_explorer:
if readonly:
return None
else:
Expand All @@ -534,7 +541,7 @@ def createEditor(self, parent, option, index):
editor.setFont(get_font(font_size_delta=DEFAULT_SMALL_DELTA))
return editor
# TextEditor for a long string
elif is_text_string(value) and len(value) > 40:
elif is_text_string(value) and len(value) > 40 and not object_explorer:
te = TextEditor(None, parent=parent)
if te.setup_and_check(value):
editor = TextEditor(value, key,
Expand All @@ -544,7 +551,7 @@ def createEditor(self, parent, option, index):
readonly=readonly))
return None
# QLineEdit for an individual value (int, float, short string, etc)
elif is_editable_type(value):
elif is_editable_type(value) and not object_explorer:
if readonly:
return None
else:
Expand All @@ -557,12 +564,26 @@ def createEditor(self, parent, option, index):
# act doesn't exist anymore.
# editor.returnPressed.connect(self.commitAndCloseEditor)
return editor
# CollectionsEditor for an arbitrary Python object
# ObjectExplorer for an arbitrary Python object
else:
editor = CollectionsEditor(parent=parent)
editor.setup(value, key, icon=self.parent().windowIcon(),
readonly=readonly)
self.create_dialog(editor, dict(model=index.model(), editor=editor,
show_callable_attributes = index.model().show_callable_attributes
show_special_attributes = index.model().show_special_attributes

if show_callable_attributes is None:
show_callable_attributes = False
if show_special_attributes is None:
show_special_attributes = False

key = index.model().keys[index.row()]
editor = ObjectExplorer(
value,
name=key,
parent=parent,
show_callable_attributes=show_callable_attributes,
show_special_attributes=show_special_attributes)
editor.sig_option_changed.connect(self.change_option)
self.create_dialog(editor, dict(model=index.model(),
editor=editor,
key=key, readonly=readonly))
return None

Expand All @@ -573,17 +594,22 @@ def create_dialog(self, editor, data):
editor.rejected.connect(
lambda eid=id(editor): self.editor_rejected(eid))
editor.show()

@Slot(str, object)
def change_option(self, option_name, new_value):
"""
Change configuration option.
This function is called when a `sig_option_changed` signal is received.
At the moment, this signal can only come from a DataFrameEditor.
At the moment, this signal can only come from a DataFrameEditor
or an ObjectExplorer.
"""
if option_name == 'dataframe_format':
self.parent().set_dataframe_format(new_value)
elif option_name == 'show_callable_attributes':
self.parent().toggle_show_callable_attributes(new_value)
elif option_name == 'show_special_attributes':
self.parent().toggle_show_special_attributes(new_value)

def editor_accepted(self, editor_id):
data = self._editors[editor_id]
Expand Down Expand Up @@ -735,6 +761,7 @@ def __init__(self, parent):
self.minmax_action = None
self.rename_action = None
self.duplicate_action = None
self.view_action = None
self.delegate = None
self.setAcceptDrops(True)
self.automatic_column_width = True
Expand Down Expand Up @@ -803,11 +830,17 @@ def setup_menu(self, minmax):
self.duplicate_action = create_action(self, _("Duplicate"),
icon=ima.icon('edit_add'),
triggered=self.duplicate_item)
self.view_action = create_action(
self,
_("View with the Object Explorer"),
icon=ima.icon('outline_explorer'),
triggered=self.view_item)
menu = QMenu(self)
menu_actions = [self.edit_action, self.plot_action, self.hist_action,
self.imshow_action, self.save_array_action,
self.insert_action, self.remove_action,
self.copy_action, self.paste_action,
self.view_action,
None, self.rename_action, self.duplicate_action,
None, resize_action, resize_columns_action]
if ndarray is not FakeObject:
Expand Down Expand Up @@ -998,6 +1031,18 @@ def dropEvent(self, event):
else:
event.ignore()

@Slot(bool)
def toggle_show_callable_attributes(self, state):
"""Toggle callable attributes for the Object Explorer."""
self.sig_option_changed.emit('show_callable_attributes', state)
self.model.show_callable_attributes = state

@Slot(bool)
def toggle_show_special_attributes(self, state):
"""Toggle special attributes for the Object Explorer."""
self.sig_option_changed.emit('show_special_attributes', state)
self.model.show_special_attributes = state

@Slot(bool)
def toggle_minmax(self, state):
"""Toggle min/max display for numpy arrays"""
Expand Down Expand Up @@ -1107,7 +1152,17 @@ def insert_item(self):
QLineEdit.Normal)
if valid and to_text_string(value):
self.new_value(key, try_to_eval(to_text_string(value)))


@Slot()
def view_item(self):
"""View item with the Object Explorer"""
index = self.currentIndex()
if not index.isValid():
return
# TODO: Remove hard coded "Value" column number (3 here)
index = index.child(index.row(), 3)
self.delegate.createEditor(self, None, index, object_explorer=True)

def __prepare_plot(self):
try:
import guiqwt.pyplot #analysis:ignore
Expand Down Expand Up @@ -1533,7 +1588,9 @@ def set_value(self, index, value):
class RemoteCollectionsEditorTableView(BaseTableView):
"""DictEditor table view"""
def __init__(self, parent, data, minmax=False, shellwidget=None,
remote_editing=False, dataframe_format=None):
remote_editing=False, dataframe_format=None,
show_callable_attributes=None,
show_special_attributes=None):
BaseTableView.__init__(self, parent)

self.shellwidget = shellwidget
Expand All @@ -1543,10 +1600,13 @@ def __init__(self, parent, data, minmax=False, shellwidget=None,
self.model = None
self.delegate = None
self.readonly = False
self.model = CollectionsModel(self, data, names=True,
minmax=minmax,
dataframe_format=dataframe_format,
remote=True)
self.model = CollectionsModel(
self, data, names=True,
minmax=minmax,
dataframe_format=dataframe_format,
show_callable_attributes=show_callable_attributes,
show_special_attributes=show_special_attributes,
remote=True)
self.setModel(self.model)

self.delegate = RemoteCollectionsDelegate(self)
Expand Down
Loading

0 comments on commit 9543582

Please sign in to comment.