Repository: streamlit/streamlit

Issue:
st.metric doesn't like numpy.int64
### Summary

I summed a column in a dataframe and passed it to `st.metric` and it threw an error because it wanted a regular int.

Should it just do the right thing?

Also: The error about being an invalid type is showing the value of the variable instead of the type that it doesn't like.

### Steps to reproduce

Code snippet:

![Screen Shot 2021-08-19 at 2 44 20 PM](https://user-images.githubusercontent.com/27847/130126932-3303d636-38f8-417d-bfa5-af2ca578a179.png)

```
   st.write(type(companies['num_developers'].sum()))
    st.markdown(f'Total developers %d' % companies['num_developers'].sum())
    st.metric('Total developers', companies['num_developers'].sum())
```

**Expected behavior:**

Expected it to just work!

**Actual behavior:**

Showed the error in the screenshot above.

### Is this a regression?

That is, did this use to work the way you expected in the past?
no

### Debug info

- Streamlit version: streamlit-nightly from today



Discussion:
Will look at this later. Currently in hackathon week! :) 
Yes, I think we should support this post-launch! Some caveats:

- We should only support this for numpy array which have a single value (maybe check using the array's shape or the [`.item` function](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.item.html)?)
- Numpy arrays can also have other data types than int/float/string, which we should **not** support

Re the error message, what about changing it to:

`'382' is of type numpy.int64, which is not an accepted type. value only accepts: int, float, str, or None`

(I'd still like to keep the 382 in there so the developer knows which value is the problem.)
Checking for `.item()` function would be an option which would work with (most) numpy values. However, there are still probably lots of corner cases that are not covered. Here are a few other suggestions: 

1) Leave it as is (only supporting native types) and improve error message telling the user to take care of converting to the right type.
2) Try to auto convert value to number, e.g. via `int(str(value))` or `float(str(value))`. 
2) Use `str(value)` in case the `value` is not a native type instead of throwing an error.

Resolving patch:
diff --git a/lib/streamlit/elements/metric.py b/lib/streamlit/elements/metric.py
--- a/lib/streamlit/elements/metric.py
+++ b/lib/streamlit/elements/metric.py
@@ -13,13 +13,13 @@
 # limitations under the License.
 
 from textwrap import dedent
-from typing import cast, Optional
+from typing import Optional, cast
 
 import attr
-
 import streamlit
 from streamlit.errors import StreamlitAPIException
 from streamlit.proto.Metric_pb2 import Metric as MetricProto
+
 from .utils import clean_text
 
 
@@ -105,7 +105,8 @@ def metric(self, label, value, delta=None, delta_color="normal"):
     def parse_label(self, label):
         if not isinstance(label, str):
             raise TypeError(
-                f"'{str(label)}' is not an accepted type. label only accepts: str"
+                f"'{str(label)}' is of type {str(type(label))}, which is not an accepted type."
+                " label only accepts: str. Please convert the label to an accepted type."
             )
         return label
 
@@ -114,11 +115,20 @@ def parse_value(self, value):
             return "—"
         if isinstance(value, float) or isinstance(value, int) or isinstance(value, str):
             return str(value)
-        else:
-            raise TypeError(
-                f"'{str(value)}' is not an accepted type. value only accepts: "
-                "int, float, str, or None"
-            )
+        elif hasattr(value, "item"):
+            # Add support for numpy values (e.g. int16, float64, etc.)
+            try:
+                # Item could also be just a variable, so we use try, except
+                if isinstance(value.item(), float) or isinstance(value.item(), int):
+                    return str(value.item())
+            except Exception:
+                pass
+
+        raise TypeError(
+            f"'{str(value)}' is of type {str(type(value))}, which is not an accepted type."
+            " value only accepts: int, float, str, or None."
+            " Please convert the value to an accepted type."
+        )
 
     def parse_delta(self, delta):
         if delta is None or delta == "":
@@ -129,8 +139,9 @@ def parse_delta(self, delta):
             return str(delta)
         else:
             raise TypeError(
-                f"'{str(delta)}' is not an accepted type. delta only accepts:"
-                " int, float, str, or None"
+                f"'{str(delta)}' is of type {str(type(delta))}, which is not an accepted type."
+                " delta only accepts: int, float, str, or None."
+                " Please convert the value to an accepted type."
             )
 
     def determine_delta_color_and_direction(self, delta_color, delta):


Test patch:
diff --git a/lib/tests/streamlit/metric_test.py b/lib/tests/streamlit/metric_test.py
--- a/lib/tests/streamlit/metric_test.py
+++ b/lib/tests/streamlit/metric_test.py
@@ -153,7 +153,8 @@ def test_invalid_label(self):
             st.metric(123, "-321")
 
         self.assertEqual(
-            "'123' is not an accepted type. label only accepts: str",
+            "'123' is of type <class 'int'>, which is not an accepted type."
+            " label only accepts: str. Please convert the label to an accepted type.",
             str(exc.exception),
         )
 
@@ -162,7 +163,8 @@ def test_invalid_value(self):
             st.metric("Testing", [1, 2, 3])
 
         self.assertEqual(
-            "'[1, 2, 3]' is not an accepted type. value only accepts: int, float, str, or None",
+            "'[1, 2, 3]' is of type <class 'list'>, which is not an accepted type."
+            " value only accepts: int, float, str, or None. Please convert the value to an accepted type.",
             str(exc.exception),
         )
 
@@ -171,7 +173,8 @@ def test_invalid_delta(self):
             st.metric("Testing", "123", [123])
 
         self.assertEqual(
-            "'[123]' is not an accepted type. delta only accepts: int, float, str, or None",
+            "'[123]' is of type <class 'list'>, which is not an accepted type."
+            " delta only accepts: int, float, str, or None. Please convert the value to an accepted type.",
             str(exc.exception),
         )