Repository: scikit-hep/awkward

Issue:
ak.type does not understand `numpy.<type>` style dtypes
### Version of Awkward Array

2.0.0rc1

### Description and code to reproduce

numpy = 1.23.4

```python3
>>> import awkward as ak
>>> import numpy as np
>>> x = np.random.normal(size=100)
>>> ak.type(x)
```
results in:
```
Traceback (most recent call last):
  File "/Users/lgray/miniforge3/envs/coffea-dev/lib/python3.8/site-packages/awkward/operations/ak_type.py", line 99, in _impl
    out = ak.types.numpytype._dtype_to_primitive_dict[array.dtype.type]
KeyError: <class 'numpy.float64'>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/lgray/miniforge3/envs/coffea-dev/lib/python3.8/site-packages/awkward/operations/ak_type.py", line 60, in type
    return _impl(array)
  File "/Users/lgray/miniforge3/envs/coffea-dev/lib/python3.8/site-packages/awkward/operations/ak_type.py", line 101, in _impl
    raise ak._errors.wrap_error(
TypeError: while calling

    ak.type(
        array = numpy.ndarray([ 0.27824033 -1.483569   -0.61108357 ...
    )

Error details: numpy array type is unrecognized by awkward: <class 'numpy.float64'>
```

`np.float64` (or `np.<type>` in general) is a fairly common way for folks to denote typing, we should probably support it.


Discussion:
Here's where it can go: in awkward/operations/ak_type.py, there's a case-catcher for

https://github.com/scikit-hep/awkward/blob/c0336973a8bacbbc86438aa69ef4c1bd48186c8b/src/awkward/operations/ak_type.py#L67-L73

The keys of that dict are actual `np.dtype` objects, but you want the `type` instances, which are obtained via `some_dtype_object.type`.

They can be identified as

```python
isinstance(array, type) and issubclass(array, np.generic)
```

Resolving patch:
diff --git a/src/awkward/operations/ak_type.py b/src/awkward/operations/ak_type.py
--- a/src/awkward/operations/ak_type.py
+++ b/src/awkward/operations/ak_type.py
@@ -1,6 +1,8 @@
 # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
 
+import builtins
 import numbers
+from datetime import datetime, timedelta
 
 import awkward as ak
 
@@ -64,15 +66,18 @@ def _impl(array):
     if array is None:
         return ak.types.UnknownType()
 
-    elif isinstance(
-        array,
-        tuple(x.type for x in ak.types.numpytype._dtype_to_primitive_dict),
+    elif isinstance(array, np.dtype):
+        return ak.types.NumpyType(ak.types.numpytype.dtype_to_primitive(array))
+
+    elif (
+        isinstance(array, np.generic)
+        or isinstance(array, builtins.type)
+        and issubclass(array, np.generic)
     ):
-        return ak.types.NumpyType(
-            ak.types.numpytype._dtype_to_primitive_dict[array.dtype]
-        )
+        primitive = ak.types.numpytype.dtype_to_primitive(np.dtype(array))
+        return ak.types.NumpyType(primitive)
 
-    elif isinstance(array, (bool, np.bool_)):
+    elif isinstance(array, bool):  # np.bool_ in np.generic (above)
         return ak.types.NumpyType("bool")
 
     elif isinstance(array, numbers.Integral):
@@ -81,6 +86,15 @@ def _impl(array):
     elif isinstance(array, numbers.Real):
         return ak.types.NumpyType("float64")
 
+    elif isinstance(array, numbers.Complex):
+        return ak.types.NumpyType("complex128")
+
+    elif isinstance(array, datetime):  # np.datetime64 in np.generic (above)
+        return ak.types.NumpyType("datetime64")
+
+    elif isinstance(array, timedelta):  # np.timedelta64 in np.generic (above)
+        return ak.types.NumpyType("timedelta")
+
     elif isinstance(
         array,
         (
@@ -95,16 +109,8 @@ def _impl(array):
         if len(array.shape) == 0:
             return _impl(array.reshape((1,))[0])
         else:
-            try:
-                out = ak.types.numpytype._dtype_to_primitive_dict[array.dtype.type]
-            except KeyError as err:
-                raise ak._errors.wrap_error(
-                    TypeError(
-                        "numpy array type is unrecognized by awkward: %r"
-                        % array.dtype.type
-                    )
-                ) from err
-            out = ak.types.NumpyType(out)
+            primitive = ak.types.numpytype.dtype_to_primitive(array.dtype)
+            out = ak.types.NumpyType(primitive)
             for x in array.shape[-1:0:-1]:
                 out = ak.types.RegularType(out, x)
             return ak.types.ArrayType(out, array.shape[0])


Test patch:
diff --git a/tests/test_1840-ak_type-to-handle-ndarray-dtype-and-nptypes.py b/tests/test_1840-ak_type-to-handle-ndarray-dtype-and-nptypes.py
new file mode 100644
--- /dev/null
+++ b/tests/test_1840-ak_type-to-handle-ndarray-dtype-and-nptypes.py
@@ -0,0 +1,23 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
+
+import numpy as np  # noqa: F401
+import pytest  # noqa: F401
+
+import awkward as ak  # noqa: F401
+
+
+def test_array():
+    array = np.random.random(size=512).astype(dtype=np.float64)
+    assert ak.type(array) == ak.types.ArrayType(ak.types.NumpyType("float64"), 512)
+
+
+def test_dtype():
+    assert ak.type(np.dtype(np.float64)) == ak.types.NumpyType("float64")
+
+
+def test_type():
+    assert ak.type(np.float64) == ak.types.NumpyType("float64")
+
+
+def test_type_instance():
+    assert ak.type(np.float64(10.0)) == ak.types.NumpyType("float64")