Repository: scikit-hep/uproot5

Issue:
Error creating fixed-length array branch in new TTree
```python
import uproot
uproot.__version__
'5.3.1'
```

I am trying to make an empty TTree with both fixed-length array branches as well as variable-length array branches, and fill it in a loop with `extend`:

```python
import awkward as ak
import uproot as ur

pt = ak.Array([[0.0, 11, 22], [], [33, 44], [55], [66, 77, 88, 99]])
event_params = [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0]]

f = ur.recreate("f.root")
f.mktree("t", {"event_params": "3 * uint32", "pt": "var * float64"})
for i in range(5):
    f["t"].extend({"event_params": [event_params[i]], "pt": ak.Array([pt[i]])})
f.close()
```

However, this script exits with the error `ValueError: format number 1 of "3 * uint32" is not recognized`. Interestingly, if the `event_params` branch is removed the script will work with the variable length `pt` branch only.

A quick fix seems to be to catch `ValueError` thrown by `numpy.dtype()` when the `3 * uint32` datashape string is passed to it, and throw a `TypeError` instead, which seems to fix the problem. However, I don't know if this causes any side effects. Any advice on fixing this error is appreciated, thanks in advance.

```diff
diff --git a/src/uproot/writing/_cascadetree.py b/src/uproot/writing/_cascadetree.py
index 5bd2201..d25a25a 100644
--- a/src/uproot/writing/_cascadetree.py
+++ b/src/uproot/writing/_cascadetree.py
@@ -121,7 +121,10 @@ class Tree:
                         raise TypeError
                     if isinstance(branch_type, str) and branch_type.strip() == "bytes":
                         raise TypeError
-                    branch_dtype = numpy.dtype(branch_type)
+                    try:
+                        branch_dtype = numpy.dtype(branch_type)
+                    except ValueError:
+                        raise TypeError
 
                 except TypeError as err:
                     try:
```



Discussion:
You're right: this block of code is first trying the `branch_type` to see if it might be convertible into a NumPy dtype, and it does so by "asking forgiveness, rather than permission": passing it to the `np.dtype` constructor and catching any errors.

We should always be catching errors as narrowly as possible (i.e. it would be bad to catch a `KeyboardInterrupt` and assume that it was because `np.dtype(branch_type)` has the wrong `branch_type`), but only catching `TypeError` is apparently too narrow. When I wrote this, I probably didn't know that the `np.dtype` constructor can fail with `ValueError` as well as `TypeError`:

```python
>>> np.dtype("var * float64")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: data type 'var * float64' not understood
>>> np.dtype("10 * float64")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jpivarski/mambaforge/lib/python3.10/site-packages/numpy/core/_internal.py", line 176, in _commastring
    raise ValueError(
ValueError: format number 1 of "10 * float64" is not recognized
```

I must have tested a `"var * float64"` case and not a `"10 * float64"` case.

The solution you propose would work, and considering the context, it is not going to have any adverse effects. However, the code would be a bit easier to read if you change line 126,

https://github.com/scikit-hep/uproot5/blob/4a79871b7f728d79cccc82533a15558a29dbf1db/src/uproot/writing/_cascadetree.py#L126

into

```python
                except (TypeError, ValueError) as err:
```

That is, catch both `TypeError` and `ValueError` instead of converting a `ValueError` into a `TypeError` and catching the latter. It makes for fewer nested `try`-`except` blocks.

Could you open a PR with this fix and tag me as a reviewer? That way, we can get it into the codebase and fix it for everybody.

Thanks!

Resolving patch:
diff --git a/src/uproot/writing/_cascadetree.py b/src/uproot/writing/_cascadetree.py
--- a/src/uproot/writing/_cascadetree.py
+++ b/src/uproot/writing/_cascadetree.py
@@ -123,7 +123,7 @@ def __init__(
                         raise TypeError
                     branch_dtype = numpy.dtype(branch_type)
 
-                except TypeError as err:
+                except (TypeError, ValueError) as err:
                     try:
                         awkward = uproot.extras.awkward()
                     except ModuleNotFoundError as err:


Test patch:
diff --git a/tests/test_1186_dtype_might_raise_ValueError.py b/tests/test_1186_dtype_might_raise_ValueError.py
new file mode 100644
--- /dev/null
+++ b/tests/test_1186_dtype_might_raise_ValueError.py
@@ -0,0 +1,23 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/uproot5/blob/main/LICENSE
+
+import os
+
+import pytest
+import numpy as np
+
+import uproot
+
+ak = pytest.importorskip("awkward")
+
+
+def test(tmp_path):
+    filename = os.path.join(tmp_path, "whatever.root")
+
+    pt = ak.Array([[0.0, 11, 22], [], [33, 44], [55], [66, 77, 88, 99]])
+    event_params = np.array(
+        [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0]], dtype=np.uint32
+    )
+
+    with uproot.recreate(filename) as f:
+        f.mktree("t", {"event_params": "3 * uint32", "pt": "var * float64"})
+        f["t"].extend({"event_params": event_params, "pt": pt})