Repository: scikit-hep/uproot5

Issue:
It's possible for a ReadOnlyDirectory to be created without all fields.
ReadOnlyDirectory has a few attributes that usually get filled with meaningful values when `fSeekKeys != 0`. But in that special case:

https://github.com/scikit-hep/uproot5/blob/914dd25ec03225c5a37da3c30d8d83d68d34fea4/src/uproot/reading.py#L1405-L1407

the `self._keys_lookup` and `self._len` attributes don't get set (any others? scroll down to check). They should be set in any case, possibly before that `if`-branch so that conditional initialization doesn't bite us again.


Discussion:
By the way, this should be back-ported to `main-v4` and make a new v4 release, not just `main`/v5. It's important enough to warrant a new v4 release.

Resolving patch:
diff --git a/src/uproot/_util.py b/src/uproot/_util.py
--- a/src/uproot/_util.py
+++ b/src/uproot/_util.py
@@ -234,7 +234,7 @@ def regularize_rename(rename):
             def applyrules(x):
                 for matcher, trans in rules:
                     if matcher.search(x) is not None:
-                        return matcher.sub(trans, x)
+                        return matcher.sub(trans, x)  # noqa: B023
                 else:
                     return x
 
diff --git a/src/uproot/behaviors/TBranch.py b/src/uproot/behaviors/TBranch.py
--- a/src/uproot/behaviors/TBranch.py
+++ b/src/uproot/behaviors/TBranch.py
@@ -546,7 +546,7 @@ def lazy(
                 is_self.append(True)
 
                 def real_filter_branch(branch):
-                    return branch is original and filter_branch(branch)
+                    return branch is original and filter_branch(branch)  # noqa: B023
 
             else:
                 is_self.append(False)
@@ -614,17 +614,17 @@ def real_filter_branch(branch):
         )
 
         def foreach(start):
-            stop = min(start + entry_step, entry_stop)
+            stop = min(start + entry_step, entry_stop)  # noqa: B023
             length = stop - start
 
             fields = []
             names = []
             for key in common_keys:
-                branch = obj[key]
+                branch = obj[key]  # noqa: B023
 
-                interpretation = branchid_interpretation[branch.cache_key]
+                interpretation = branchid_interpretation[branch.cache_key]  # noqa: B023
                 form = interpretation.awkward_form(
-                    obj.file,
+                    obj.file,  # noqa: B023
                     {
                         "index_format": "i64",
                         "header": False,
@@ -637,7 +637,7 @@ def foreach(start):
                 )
 
                 generator = awkward.layout.ArrayGenerator(
-                    branch.array,
+                    branch.array,  # noqa: B023
                     (
                         None,
                         start,
@@ -654,7 +654,7 @@ def foreach(start):
                     length,
                 )
                 cache_key = "{}:{}:{}-{}:{}".format(
-                    branch.cache_key,
+                    branch.cache_key,  # noqa: B023
                     interpretation.cache_key,
                     start,
                     stop,
@@ -665,7 +665,7 @@ def foreach(start):
                     generator, cache=array_cache, cache_key=cache_key
                 )
                 fields.append(virtualarray)
-                names.append(key)
+                names.append(key)  # noqa: B023
 
             recordarray = awkward.layout.RecordArray(fields, names, length)
             partitions.append(recordarray)
@@ -3594,7 +3594,7 @@ def _get_dask_array(
                 is_self.append(True)
 
                 def real_filter_branch(branch):
-                    return branch is original and filter_branch(branch)
+                    return branch is original and filter_branch(branch)  # noqa: B023
 
             else:
                 is_self.append(False)
@@ -3671,13 +3671,13 @@ def delayed_get_array(ttree, key, start, stop):
                 dt, inner_shape = dt.subdtype
 
             def foreach(start):
-                stop = min(start + entry_step, entry_stop)
+                stop = min(start + entry_step, entry_stop)  # noqa: B023
                 length = stop - start
 
-                delayed_array = delayed_get_array(ttree, key, start, stop)
-                shape = (length,) + inner_shape
-                dask_arrays.append(
-                    da.from_delayed(delayed_array, shape=shape, dtype=dt)
+                delayed_array = delayed_get_array(ttree, key, start, stop)  # noqa: B023
+                shape = (length,) + inner_shape  # noqa: B023
+                dask_arrays.append(  # noqa: B023
+                    da.from_delayed(delayed_array, shape=shape, dtype=dt)  # noqa: B023
                 )
 
             for start in range(entry_start, entry_stop, entry_step):
diff --git a/src/uproot/reading.py b/src/uproot/reading.py
--- a/src/uproot/reading.py
+++ b/src/uproot/reading.py
@@ -1405,7 +1405,8 @@ def __init__(self, path, cursor, context, file, parent):
         if self._fSeekKeys == 0:
             self._header_key = None
             self._keys = []
-
+            self._keys_lookup = {}
+            self._len = None
         else:
             keys_start = self._fSeekKeys
             keys_stop = min(keys_start + self._fNbytesKeys + 8, file.fEND)


Test patch: