Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add an Object Explorer to the Variable Explorer #8852

Merged
merged 47 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
68f4270
Object Explorer: Initial implementation
dalthviz Feb 27, 2019
0e26fa8
Object Explorer: Divide module
dalthviz Mar 3, 2019
50750a6
Object Explorer: Change log imports
dalthviz Mar 3, 2019
5a22f76
Object Explorer: Change TreeItem import
dalthviz Mar 3, 2019
5fb8e8d
Object Explorer: Use object explorer instead of CollectionsEditor
dalthviz Mar 3, 2019
d2df5c5
Testing
dalthviz Mar 4, 2019
df303d7
Object Explorer: Use py3compat instead of six
dalthviz Mar 5, 2019
1c295d1
Object Explorer: Fix file headers, docstrings and NOTICE.txt
dalthviz Mar 14, 2019
3963e13
Object Explorer: Remove Help menu and about message box
dalthviz Mar 14, 2019
a1a63ca
Object Explorer: Change default Details view to pretty print
dalthviz Mar 14, 2019
ae9d4bb
Object Explorer: UI color. Translation for strings. Default values
dalthviz Mar 17, 2019
bb87bd9
Object Explorer: Expand object one level
dalthviz Mar 17, 2019
89058ea
Object Explorer: Fix style issue
dalthviz Mar 17, 2019
8cdd9c1
Object Explorer: Change default size and column resize to contents
dalthviz Mar 19, 2019
ef924a6
Object Explorer: Initial change of QMenubar to toolbar buttons
dalthviz Mar 19, 2019
6224e5e
Object Explorer: Use config from Spyder. Fix attribute columns menu
dalthviz Mar 22, 2019
ea3d5bd
Object Explorer: Save state of actions
dalthviz Mar 25, 2019
ad1045a
Objec Explorer: Rename of module
dalthviz Mar 27, 2019
3d5b4aa
Object Explorer: Update object explorer refresh_rate
dalthviz Apr 6, 2019
4491476
Object Explorer: Fix toolbuttons and handle remote settings
dalthviz Apr 9, 2019
4714edb
Object Explorer: Fix call to create_explorer
dalthviz Apr 9, 2019
f8e3a6f
Merge branch 'master' into fixes_issue_558
dalthviz Apr 9, 2019
c6e6112
Object Explorer: Fix create_explorer
dalthviz Apr 12, 2019
9c87c4f
Object Explorer: Fix Variable Explorer settings test
dalthviz Jun 8, 2019
d39e905
Object Explorer: Fix editor_parent test for object explorer
dalthviz Jun 9, 2019
3e1f156
Object Explorer: Add validations for the menu setup
dalthviz Jun 9, 2019
cd57e9e
Object Explorer: Set default values for configs
dalthviz Jun 9, 2019
f4b14e5
Object Explorer: Fix editor_parent_set test for object explorer
dalthviz Jun 9, 2019
74112b3
Object Explorer: Add initial tests for the object explorer
dalthviz Jun 13, 2019
7869328
Object Explorer: Add menu action to use Object Explorer for any variable
dalthviz Jun 13, 2019
518c5f8
Object Explorer: Mark test to by skipped in Python 2
dalthviz Jun 13, 2019
6e8bdfe
Object Explorer: Change default elements and general adjustments
dalthviz Jun 21, 2019
6bae351
Object Explorer: Adjust tests
dalthviz Jun 21, 2019
8ecbd99
Object Explorer: Undo Variable Explorer test_get_settings changes
dalthviz Jun 22, 2019
404cbba
Object Explorer: Use CodeEditor instance for details
dalthviz Jun 22, 2019
4ba6d13
Merge branch 'master' into fixes_issue_558
dalthviz Jun 22, 2019
c97b214
Object Explorer: Set correct language to the CodeEditor instance
dalthviz Jun 22, 2019
d8fb818
Object Explorer: Add 'Value' column using valu_to_display function
dalthviz Jun 22, 2019
027f5eb
Object Explorer: Fix test for new column
dalthviz Jun 22, 2019
059a6dd
Object Explorer: Update NOTICE.txt, code style and details
dalthviz Jun 22, 2019
b0677ae
Object Explorer: Add rule for CircleCI change docstring and test import
dalthviz Jun 22, 2019
b425d50
Requirements: Update minimum version of spyder-kernels
dalthviz Jun 23, 2019
61e8ba9
Object explorer: Simplify some imports
ccordoba12 Jun 24, 2019
e53ed22
Object Explorer: Move repr to be the last entry in Details
ccordoba12 Jun 24, 2019
b8f2775
Object Explorer: Don't show markers in CodeEditor instance
ccordoba12 Jun 24, 2019
a8526a6
Object Explorer: Change default size
ccordoba12 Jun 24, 2019
de751fe
Testing: Fix test_get_settings
ccordoba12 Jun 24, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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