Repository: spyder-ide/spyder

Issue:
View variable explorer supported objects in the object explorer

## Problem Description
Currently there is no way to view the `dtypes` of `DataFrame` in `Object Explorer` for example in read only mode to see the `dtypes` of each of the columns of a `DataFrame`.  Because `Object Explorer` only populates attributes about objects without a specific editor inside Spyder, and `DataFrame` has the `DataFrameEditor` there is no way to view object attributes using the `Object Explorer`.  This could be a helpful addition to the `Object Explorer`.  

[Here is an example](https://www.spyder-ide.org/blog/spyder-variable-explorer/view-object-explorer.png) of what you can see about a `DataFrame`  If the `DataFrame` is selected the wonderful `DataFrameEditor` comes up. but the user is not able to simply see the `dtypes` of the columns. [Here is the editor view. ](https://www.spyder-ide.org/blog/spyder-variable-explorer/multi-index-support.png)

This was my first "issue" posted, @dalthviz suggested I post.  Hopefully, it was helpful and cogent.

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: Spyder 4.1.1
* Python version: 3.7
* Qt version: 
* PyQt version: 
* Operating System name/version: Mac OS 10.15.3




Discussion:
@dalthviz, @eervin123, I don't understand very well how to add this functionality. Could you explain better how and/or in what viewer we should add it?
@ccordoba12 I think the petition is to enable showing for example the `dtypes` in the object explorer when a `DataFrame` is open using it. Currently, when you open a`DataFrame` the only thing you see is an entry for the `DataFrame` but no its attributes (like the `dtypes`):

Example `DataFrame` snippet:

```python
import pandas as pd
df = pd.DataFrame({'float': [1.0],

                   'int': [1],

                   'datetime': [pd.Timestamp('20180310')],

                   'string': ['foo']})
```

![image](https://user-images.githubusercontent.com/16781833/78820087-da25f080-799c-11ea-8009-f7ac8ecbf015.png)

This could be caused since we are only adding the attributes of the object if Spyder doesn't have an editor for the object type:

https://github.com/spyder-ide/spyder/blob/f04d357dc0d19fa8ece25b1d922e8245cb20452a/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py#L286-L290

If I remove the validation I can navigate through the attributes of the `DataFrame` and see, for example, the `dtypes` of the columns:

![image](https://user-images.githubusercontent.com/16781833/78821333-ed39c000-799e-11ea-980d-ab7976742e8f.png)

Thank you @dalthviz much more articulate than I could have been.
> This could be caused since we are only adding the attributes of the object if Spyder doesn't have an editor for the object type

Ok, I see. Then I think we should remove that restriction, given that it's useful to show the attributes of our currently supported objects, as @eervin123 pointed out.

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py
--- a/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py
+++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/objectexplorer.py
@@ -322,8 +322,6 @@ def _setup_views(self):
         self.central_splitter.setCollapsible(0, False)
         self.central_splitter.setCollapsible(1, True)
         self.central_splitter.setSizes([500, 320])
-        self.central_splitter.setStretchFactor(0, 10)
-        self.central_splitter.setStretchFactor(1, 0)
 
         # Connect signals
         # Keep a temporary reference of the selection_model to prevent
diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py
--- a/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py
+++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/tree_model.py
@@ -284,29 +284,27 @@ def _fetchObjectChildren(self, obj, obj_path):
         path_strings = []
         tree_items = []
 
-        # Only populate children for objects without their own editor
-        if not is_editable_type(obj):
-            is_attr_list = [False] * len(obj_children)
-
-            # Object attributes
-            # Needed to handle errors while getting object's attributes
-            # Related with spyder-ide/spyder#6728 and spyder-ide/spyder#9959
-            for attr_name in dir(obj):
-                try:
-                    attr_value = getattr(obj, attr_name)
-                    obj_children.append((attr_name, attr_value))
-                    path_strings.append('{}.{}'.format(obj_path, attr_name)
-                                        if obj_path else attr_name)
-                    is_attr_list.append(True)
-                except Exception:
-                    # Attribute could not be get
-                    pass
-            assert len(obj_children) == len(path_strings), "sanity check"
-
-            for item, path_str, is_attr in zip(obj_children, path_strings,
-                                               is_attr_list):
-                name, child_obj = item
-                tree_items.append(TreeItem(child_obj, name, path_str, is_attr))
+        is_attr_list = [False] * len(obj_children)
+
+        # Object attributes
+        # Needed to handle errors while getting object's attributes
+        # Related with spyder-ide/spyder#6728 and spyder-ide/spyder#9959
+        for attr_name in dir(obj):
+            try:
+                attr_value = getattr(obj, attr_name)
+                obj_children.append((attr_name, attr_value))
+                path_strings.append('{}.{}'.format(obj_path, attr_name)
+                                    if obj_path else attr_name)
+                is_attr_list.append(True)
+            except Exception:
+                # Attribute could not be get
+                pass
+        assert len(obj_children) == len(path_strings), "sanity check"
+
+        for item, path_str, is_attr in zip(obj_children, path_strings,
+                                           is_attr_list):
+            name, child_obj = item
+            tree_items.append(TreeItem(child_obj, name, path_str, is_attr))
 
         return tree_items
 


Test patch:
diff --git a/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py b/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py
--- a/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py
+++ b/spyder/plugins/variableexplorer/widgets/objectexplorer/tests/test_objectexplorer.py
@@ -107,17 +107,17 @@ def error_attribute(self):
 
 
 @pytest.mark.parametrize('params', [
-            'kjkj kj k j j kj k jkj',
-            [1, 3, 4, 'kjkj', None],
-            {1, 2, 1, 3, None, 'A', 'B', 'C', True, False},
-            1.2233,
-            np.random.rand(10, 10),
-            datetime.date(1945, 5, 8),
-            datetime.datetime(1945, 5, 8)
+            # variable to show, rowCount for python 3 and 2
+            ('kjkj kj k j j kj k jkj', [71, 78]),
+            ([1, 3, 4, 'kjkj', None], [45, 46]),
+            ({1, 2, 1, 3, None, 'A', 'B', 'C', True, False}, [54, 55]),
+            (1.2233, [57, 58]),
+            (np.random.rand(10, 10), [166, 162]),
+            (datetime.date(1945, 5, 8), [43, 46])
         ])
 def test_objectexplorer_collection_types(objectexplorer, params):
     """Test to validate proper handling of collection data types."""
-    test = params
+    test, row_count = params
     editor = objectexplorer(test,
                             name='variable',
                             show_callable_attributes=True,
@@ -129,8 +129,8 @@ def test_objectexplorer_collection_types(objectexplorer, params):
     model = editor.obj_tree.model()
     # The row for the variable
     assert model.rowCount() == 1
-    # Root row without children
-    assert model.rowCount(model.index(0, 0)) == 0
+    # Root row with children
+    assert model.rowCount(model.index(0, 0)) in row_count
     assert model.columnCount() == 11