Repository: spyder-ide/spyder

Issue:
Variable Explorer throws an error instead of displaying contents when attempting to inspect any loaded modules
## Description

I double-clicked a module which was the value of a `dict` in variable explorer and got an error.

**What steps will reproduce the problem?**

1. Import a module (example: `import os`)
2. Initialize a `dict` in a variable (example: `d=dict()`)
3. Assign the module to the dict (example: `d['os']=os`)
4. Double-click the cyan entry of your variable on variable explorer
5. Double-click the white entry which corresponds to your module
6. You see the error reporting window.

**What is the expected output? What do you see instead?**

I expected seeing the global variables defined in the module, but I got an error reporting window.

**Please provide any additional information below**

#### Stack trace
```
  File "/usr/local/lib/python3.6/dist-packages/spyder/widgets/variableexplorer/collectionseditor.py", line 513, in createEditor
    readonly=readonly)
  File "/usr/local/lib/python3.6/dist-packages/spyder/widgets/variableexplorer/collectionseditor.py", line 1315, in setup
    self.data_copy = copy.deepcopy(data)
  File "/usr/lib/python3.6/copy.py", line 169, in deepcopy
    rv = reductor(4)
TypeError: can't pickle module objects
```

#### Screenshot
![screenshot_2017-12-28_16-23-15](https://user-images.githubusercontent.com/12143895/34419520-7fa595ac-ebeb-11e7-8b13-ed98cb5c49ac.png)

## Version and main components

* Ubuntu Studio 17.10
* Spyder Version: 3.2.5 
* Python Version: 3.6.3
* Qt Versions: 5.9.3, PyQt5 5.9.2 on Linux

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.21.1 (OK)
numpy >=1.7      :  1.12.1 (OK)
sphinx >=0.6.6   :  1.6.5 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
psutil >=0.3     :  5.4.2 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.27.3 (OK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.8.1 (OK)

```



Discussion:
Thanks for reporting. I reproduced this with your minimal example:
```
import os
d = dict()
d["os_test"] = os
d["other_test"] = "Other"
```
This actually appears to be changed from Spyder 3.2.4; in the former version, the dictionary simply didn't open at all in a CollectionsEditor; in the latter, it behaved as you described, both opening the dictionary itself and then trying and failing the module object (while succeeding in editing "Other"). 

Looking at the error trace, when a `CollectionsEditor` is opened on a non-standard object like a module, it does a `deepcopy()` of the collection and the objects within, presumably such that it can either save the modified state on an "Ok", or discard it on "Cancel", etc. However, that doesn't work on a module for internal python reasons, so it throws an uncaught error. I'm taking a look at the code right to figure out how to best handle it; we should be able to do _something_ about it.
@CAM-Gerlach, it should be enough with catching the `TypeError` generated by this and then `pass` if that happens.
@ccordoba12 Yup, that's just what I was trying right now!
Great! Thanks for taking a look at it!

El 28/12/17 a las 15:53, C.A.M. Gerlach escribió:
>
> @ccordoba12 <https://github.com/ccordoba12> Yup, that's just what I 
> was trying right now!
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub 
> <https://github.com/spyder-ide/spyder/issues/6080#issuecomment-354356249>, 
> or mute the thread 
> <https://github.com/notifications/unsubscribe-auth/AAWS7Q67APS1aXcPr5DgSoDJ4kYHilCfks5tE_-0gaJpZM4ROi2O>.
>


Just a tad more complicated than that (and had to deal with an unrelated conda-env issue), but I got it working just fine without much trouble. I assume I should add a test for it?

Resolving patch:
diff --git a/spyder/widgets/variableexplorer/collectionseditor.py b/spyder/widgets/variableexplorer/collectionseditor.py
--- a/spyder/widgets/variableexplorer/collectionseditor.py
+++ b/spyder/widgets/variableexplorer/collectionseditor.py
@@ -1318,14 +1318,18 @@ def setup(self, data, title='', readonly=False, width=650, remote=False,
                 self.data_copy = copy.deepcopy(data)
             except NotImplementedError:
                 self.data_copy = copy.copy(data)
+            except (TypeError, AttributeError):
+                readonly = True
+                self.data_copy = data
             datalen = len(get_object_attrs(data))
-        self.widget = CollectionsEditorWidget(self, self.data_copy, title=title,
-                                              readonly=readonly, remote=remote)
+        self.widget = CollectionsEditorWidget(self, self.data_copy,
+                                              title=title, readonly=readonly,
+                                              remote=remote)
 
         layout = QVBoxLayout()
         layout.addWidget(self.widget)
         self.setLayout(layout)
-        
+
         # Buttons configuration
         buttons = QDialogButtonBox.Ok
         if not readonly:


Test patch:
diff --git a/spyder/widgets/variableexplorer/tests/test_collectioneditor.py b/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
--- a/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
+++ b/spyder/widgets/variableexplorer/tests/test_collectioneditor.py
@@ -8,6 +8,7 @@
 """
 
 # Standard library imports
+import os  # Example module for testing display inside collecitoneditor
 import copy
 import datetime
 try:
@@ -18,10 +19,13 @@
 # Third party imports
 import pandas
 import pytest
+from flaky import flaky
 
 # Local imports
 from spyder.widgets.variableexplorer.collectionseditor import (
-    CollectionsEditorTableView, CollectionsModel, LARGE_NROWS, ROWS_TO_LOAD)
+    CollectionsEditorTableView, CollectionsModel, CollectionsEditor,
+    LARGE_NROWS, ROWS_TO_LOAD)
+
 
 # Helper functions
 def data(cm, i, j):
@@ -162,9 +166,10 @@ def test_rename_and_duplicate_item_in_collection_editor():
             assert editor.model.get_data() == coll_copy + [coll_copy[0]]
 
 
-def test_edit_mutable_and_immutable_types(qtbot, monkeypatch):
-    # To ensure mutable types (lists, dicts) and individual values are editable
-    # But not immutable ones (tuples) or anything inside of them, per #5991
+def test_edit_mutable_and_immutable_types(monkeypatch):
+    """Check to ensure mutable types (lists, dicts) and individual values are
+    editable, but not immutable ones (tuples) or anything inside of them,
+    per #5991"""
     MockQLineEdit = Mock()
     attr_to_patch_qlineedit = ('spyder.widgets.variableexplorer.' +
                                'collectionseditor.QLineEdit')
@@ -265,5 +270,14 @@ def test_edit_mutable_and_immutable_types(qtbot, monkeypatch):
                                                             readonly=True)
 
 
+@flaky(max_runs=3)
+def test_view_module_in_coledit():
+    """Check that modules don't produce an error when trying to open them in
+    Variable Explorer, and are set as readonly. Regression test for #6080"""
+    editor = CollectionsEditor()
+    editor.setup(os, "module_test", readonly=False)
+    assert editor.widget.editor.readonly
+
+
 if __name__ == "__main__":
     pytest.main()