Repository: spyder-ide/spyder

Issue:
ndarray subclasses with a member called 'dtype' crashes Spyder if dtype.name doesn't exist
### What steps will reproduce the problem?

<!--- You can use Markdown here --->

A very simplified example:

```
class my_array(np.ndarray):
    # Define the fields of the structured array
    dtype = 'f8'

x = my_array((2,3))
```

This will crash.

### Traceback
```python-traceback
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'list' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'str' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'str' object has no attribute 'name'
Exception in comms call get_namespace_view:
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/console/kernel.py", line 193, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 688, in make_remote_view
    'type':  get_human_readable_type(value),
  File "/Users/mike/Library/miniconda3/envs/spyder-env/lib/python3.10/site-packages/spyder_kernels/utils/nsview.py", line 538, in get_human_readable_type
    return u'Array of ' + item.dtype.name
AttributeError: 'str' object has no attribute 'name'
```

## Versions

* Spyder version: 5.3.2 7235126e0 (standalone)
* Python version: 3.9.5 64-bit
* Qt version: 5.15.2
* PyQt5 version: 5.15.7
* Operating System: Darwin 21.6.0

### Dependencies

```
# Mandatory:
atomicwrites >=1.2.0                 :  1.4.1 (OK)
chardet >=2.0.0                      :  5.0.0 (OK)
cloudpickle >=0.5.0                  :  2.1.0 (OK)
cookiecutter >=1.6.0                 :  2.1.1 (OK)
diff_match_patch >=20181111          :  20200713 (OK)
intervaltree >=3.0.2                 :  3.1.0 (OK)
IPython >=7.31.1;<8.0.0              :  7.34.0 (OK)
jedi >=0.17.2;<0.19.0                :  0.18.1 (OK)
jellyfish >=0.7                      :  0.9.0 (OK)
jsonschema >=3.2.0                   :  4.7.2 (OK)
keyring >=17.0.0                     :  23.7.0 (OK)
nbconvert >=4.0                      :  6.5.0 (OK)
numpydoc >=0.6.0                     :  1.4.0 (OK)
parso >=0.7.0;<0.9.0                 :  0.8.3 (OK)
pexpect >=4.4.0                      :  4.8.0 (OK)
pickleshare >=0.4                    :  0.7.5 (OK)
psutil >=5.3                         :  5.9.1 (OK)
pygments >=2.0                       :  2.12.0 (OK)
pylint >=2.5.0;<3.0                  :  2.14.4 (OK)
pyls_spyder >=0.4.0                  :  0.4.0 (OK)
pylsp >=1.5.0;<1.6.0                 :  1.5.0 (OK)
pylsp_black >=1.2.0                  :  1.2.1 (OK)
qdarkstyle >=3.0.2;<3.1.0            :  3.0.3 (OK)
qstylizer >=0.1.10                   :  0.2.1 (OK)
qtawesome >=1.0.2                    :  1.1.1 (OK)
qtconsole >=5.3.0;<5.4.0             :  5.3.1 (OK)
qtpy >=2.1.0                         :  2.1.0 (OK)
rtree >=0.9.7                        :  1.0.0 (OK)
setuptools >=49.6.0                  :  63.2.0 (OK)
sphinx >=0.6.6                       :  5.0.2 (OK)
spyder_kernels >=2.3.2;<2.4.0        :  2.3.2 (OK)
textdistance >=4.2.0                 :  4.3.0 (OK)
three_merge >=0.1.1                  :  0.1.1 (OK)
watchdog >=0.10.3                    :  2.1.9 (OK)
zmq >=22.1.0                         :  23.2.0 (OK)

# Optional:
cython >=0.21                        :  0.29.30 (OK)
matplotlib >=3.0.0                   :  3.5.2 (OK)
numpy >=1.7                          :  1.22.4 (OK)
pandas >=1.1.1                       :  1.4.3 (OK)
scipy >=0.17.0                       :  1.8.1 (OK)
sympy >=0.7.3                        :  1.10.1 (OK)

# Spyder plugins:
spyder_terminal.terminalplugin 1.2.2 :  1.2.2 (OK)
```



Discussion:
Hey @battaglia01, thanks for reporting. This is fixed in our latest version (5.4.2), so please update. I don't know if you're using Conda-forge packages, but that's the only way to get 5.4.2 at the moment.

However, after tried to open the array you created in the Variable Explorer, I got the following error:

```python-traceback
Traceback (most recent call last):
  File "/home/carlos/Projects/spyder/spyder/spyder/plugins/variableexplorer/widgets/collectionsdelegate.py", line 182, in createEditor
    if not editor.setup_and_check(value, title=key, readonly=readonly):
  File "/home/carlos/Projects/spyder/spyder/spyder/plugins/variableexplorer/widgets/arrayeditor.py", line 662, in setup_and_check
    is_record_array = data.dtype.names is not None
AttributeError: 'str' object has no attribute 'names'
```

which is similar to the one you reported. So, we'll fix it in our next version (5.4.3), to be released in March.

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
--- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
@@ -659,9 +659,16 @@ def setup_and_check(self, data, title='', readonly=False,
         """
         self.data = data
         readonly = readonly or not self.data.flags.writeable
-        is_record_array = data.dtype.names is not None
         is_masked_array = isinstance(data, np.ma.MaskedArray)
 
+        # This is necessary in case users subclass ndarray and set the dtype
+        # to an object that is not an actual dtype.
+        # Fixes spyder-ide/spyder#20462
+        if hasattr(data.dtype, 'names'):
+            is_record_array = data.dtype.names is not None
+        else:
+            is_record_array = False
+
         if data.ndim > 3:
             self.error(_("Arrays with more than 3 dimensions are not "
                          "supported"))
@@ -675,7 +682,14 @@ def setup_and_check(self, data, title='', readonly=False,
                          "number"))
             return False
         if not is_record_array:
-            dtn = data.dtype.name
+            # This is necessary in case users subclass ndarray and set the
+            # dtype to an object that is not an actual dtype.
+            # Fixes spyder-ide/spyder#20462
+            if hasattr(data.dtype, 'name'):
+                dtn = data.dtype.name
+            else:
+                dtn = 'Unknown'
+
             if dtn == 'object':
                 # If the array doesn't have shape, we can't display it
                 if data.shape == ():
@@ -687,7 +701,7 @@ def setup_and_check(self, data, title='', readonly=False,
                 self.readonly = readonly = True
             elif (dtn not in SUPPORTED_FORMATS and not dtn.startswith('str')
                     and not dtn.startswith('unicode')):
-                arr = _("%s arrays") % data.dtype.name
+                arr = _("%s arrays") % dtn
                 self.error(_("%s are currently not supported") % arr)
                 return False
 
diff --git a/spyder/plugins/variableexplorer/widgets/collectionsdelegate.py b/spyder/plugins/variableexplorer/widgets/collectionsdelegate.py
--- a/spyder/plugins/variableexplorer/widgets/collectionsdelegate.py
+++ b/spyder/plugins/variableexplorer/widgets/collectionsdelegate.py
@@ -180,6 +180,7 @@ def createEditor(self, parent, option, index, object_explorer=False):
             from .arrayeditor import ArrayEditor
             editor = ArrayEditor(parent=parent)
             if not editor.setup_and_check(value, title=key, readonly=readonly):
+                self.sig_editor_shown.emit()
                 return
             self.create_dialog(editor, dict(model=index.model(), editor=editor,
                                             key=key, readonly=readonly))
@@ -196,6 +197,7 @@ def createEditor(self, parent, option, index, object_explorer=False):
             arr = np.array(value)
             editor = ArrayEditor(parent=parent)
             if not editor.setup_and_check(arr, title=key, readonly=readonly):
+                self.sig_editor_shown.emit()
                 return
             conv_func = lambda arr: PIL.Image.fromarray(arr, mode=value.mode)
             self.create_dialog(editor, dict(model=index.model(), editor=editor,


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_formatting.py b/spyder/plugins/editor/widgets/tests/test_formatting.py
--- a/spyder/plugins/editor/widgets/tests/test_formatting.py
+++ b/spyder/plugins/editor/widgets/tests/test_formatting.py
@@ -12,6 +12,7 @@
 import random
 
 # Third party imports
+from flaky import flaky
 import pytest
 from qtpy.QtCore import Qt
 from qtpy.QtGui import QTextCursor
@@ -257,6 +258,7 @@ def test_closing_document_formatting(
     assert code_editor.get_text_with_eol() == expected
 
 
+@flaky(max_runs=5)
 @pytest.mark.order(1)
 @pytest.mark.parametrize('formatter', [autopep8, black])
 def test_formatting_on_save(completions_editor, formatter, qtbot):
@@ -306,7 +308,7 @@ def test_formatting_on_save(completions_editor, formatter, qtbot):
     with qtbot.waitSignal(
             code_editor.completions_response_signal, timeout=30000):
         editorstack.save(force=True)
-    qtbot.wait(500)
+    qtbot.wait(1000)
 
     # Check that auto-formatting was applied on save and that we restored the
     # previous line.