Repository: yt-project/yt

Issue:
BUG: ImportError when calling a repr in Jupyter
### Bug report

**Bug summary**
In a Jupyter notebook file, ending a cell with e.g. `ds.domain_width` (implicitly calling for a repr)  will fail with an `ImportError` if `ipywidgets` isn't installed.
This seems like an unreasonable requirement for a simple repr, I think it should simply fallback to using the default `unyt_array.__repr__` method if a widget isn't on the menu.

**Code for reproduction**

```python
import yt
ds = yt.load_sample("IsolatedGalaxy")
ds.domain_width
```
fails with an `ImportError`


**Actual outcome**

```python
╭──────────────────────────── Traceback (most recent call last) ────────────────────────────╮
│ /Users/robcleme/.pyenv/versions/3.9.5/envs/yt-dev/lib/python3.9/site-packages/IPython/cor │
│ e/formatters.py:224 in catch_format_error                                                 │
│                                                                                           │
│    221 def catch_format_error(method, self, *args, **kwargs):                             │
│    222 │   """show traceback on failed format call"""                                     │
│    223 │   try:                                                                           │
│ ❱  224 │   │   r = method(self, *args, **kwargs)                                          │
│    225 │   except NotImplementedError:                                                    │
│    226 │   │   # don't warn on NotImplementedErrors                                       │
│    227 │   │   return self._check_return(None, args[0])                                   │
│                                                                                           │
│ /Users/robcleme/.pyenv/versions/3.9.5/envs/yt-dev/lib/python3.9/site-packages/IPython/cor │
│ e/formatters.py:918 in __call__                                                           │
│                                                                                           │
│    915 │   │   │   # Finally look for special method names                                │
│    916 │   │   │   method = get_real_method(obj, self.print_method)                       │
│    917 │   │   │   if method is not None:                                                 │
│ ❱  918 │   │   │   │   method()                                                           │
│    919 │   │   │   │   return True                                                        │
│    920                                                                                    │
│    921                                                                                    │
│                                                                                           │
│ /Users/robcleme/dev/yt-project/yt/yt/units/__init__.py:121 in _wrap_display_ytarray       │
│                                                                                           │
│   118 def _wrap_display_ytarray(arr):                                                     │
│   119 │   from IPython.core.display import display                                        │
│   120 │                                                                                   │
│ ❱ 121 │   display(display_ytarray(arr))                                                   │
│   122                                                                                     │
│                                                                                           │
│ /Users/robcleme/dev/yt-project/yt/yt/units/__init__.py:93 in display_ytarray              │
│                                                                                           │
│    90 │   """                                                                             │
│    91 │   if arr.size > 3:                                                                │
│    92 │   │   raise YTArrayTooLargeToDisplay(arr.size, 3)                                 │
│ ❱  93 │   import ipywidgets                                                               │
│    94 │                                                                                   │
│    95 │   unit_registry = arr.units.registry                                              │
│    96 │   equiv = unit_registry.list_same_dimensions(arr.units)                           │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
ModuleNotFoundError: No module named 'ipywidgets'

unyt_array([1., 1., 1.], 'code_length')
```
so the repr is actually called in the end, but the output is dominated by an error message.

**Expected outcome**

I'd expect the repr to be called and the error swallowed silently.



Discussion:

Resolving patch:
diff --git a/yt/data_objects/static_output.py b/yt/data_objects/static_output.py
--- a/yt/data_objects/static_output.py
+++ b/yt/data_objects/static_output.py
@@ -6,6 +6,7 @@
 import time
 import weakref
 from collections import defaultdict
+from importlib.util import find_spec
 from stat import ST_CTIME
 
 import numpy as np
@@ -80,7 +81,7 @@ def __get__(self, instance, owner):
             ret = ret.copy()
         except AttributeError:
             pass
-        if self.display_array:
+        if self.display_array and find_spec("ipywidgets") is not None:
             try:
                 ret._ipython_display_ = functools.partial(_wrap_display_ytarray, ret)
             # This will error out if the items have yet to be turned into


Test patch: