Repository: plotly/dash

Issue:
[BUG] NoUpdate.is_no_update can't handle ndarray
**Describe your context**
currently running project using dash for data display, callbacks pass ndarrays of certain sections of data to view

```
dash                      2.6.0
dash-bootstrap-components 1.2.0
dash-core-components      2.0.0
dash-daq                  0.5.0
dash-extensions           0.1.5
dash-html-components      2.0.0
dash-table                5.0.0
```

**Describe the bug**
if callback passes an ndarray, NoUpdate.is_no_update  on line 441 of _callback.py raises a ValueError, this issue was not seen in previous versions.
```
[2022-07-31 16:27:03,570] ERROR in app: Exception on /_dash-update-component [POST]
Traceback (most recent call last):
  File "...\venv\lib\site-packages\flask\app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
  File "...\venv\lib\site-packages\flask\app.py", line 1525, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "...\venv\lib\site-packages\flask\app.py", line 1523, in full_dispatch_request
    rv = self.dispatch_request()
  File "...\venv\lib\site-packages\flask\app.py", line 1509, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "...\venv\lib\site-packages\dash\dash.py", line 1260, in dispatch
    ctx.run(
  File "...\venv\lib\site-packages\dash\_callback.py", line 441, in add_context
    if NoUpdate.is_no_update(output_value):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```

Workaround for this is wrapping ndarray with list, like this:
```
return [myndArr]
```





Discussion:

Resolving patch:
diff --git a/dash/_callback.py b/dash/_callback.py
--- a/dash/_callback.py
+++ b/dash/_callback.py
@@ -40,9 +40,9 @@ def to_plotly_json(self):  # pylint: disable=no-self-use
 
     @staticmethod
     def is_no_update(obj):
-        return isinstance(obj, NoUpdate) or obj == {
-            "_dash_no_update": "_dash_no_update"
-        }
+        return isinstance(obj, NoUpdate) or (
+            isinstance(obj, dict) and obj == {"_dash_no_update": "_dash_no_update"}
+        )
 
 
 GLOBAL_CALLBACK_LIST = []


Test patch:
diff --git a/tests/integration/callbacks/test_basic_callback.py b/tests/integration/callbacks/test_basic_callback.py
--- a/tests/integration/callbacks/test_basic_callback.py
+++ b/tests/integration/callbacks/test_basic_callback.py
@@ -4,6 +4,7 @@
 import pytest
 import time
 
+import numpy as np
 import werkzeug
 
 from dash_test_components import (
@@ -765,3 +766,19 @@ def update_output(value):
         return f"returning {value}"
 
     assert update_output("my-value") == "returning my-value"
+
+
+def test_cbsc018_callback_ndarray_output(dash_duo):
+    app = Dash(__name__)
+    app.layout = html.Div([dcc.Store(id="output"), html.Button("click", id="clicker")])
+
+    @app.callback(
+        Output("output", "data"),
+        Input("clicker", "n_clicks"),
+    )
+    def on_click(_):
+        return np.array([[1, 2, 3], [4, 5, 6]], np.int32)
+
+    dash_duo.start_server(app)
+
+    assert dash_duo.get_logs() == []