🤖 AI text below 🤖
Split out of #1713, which now tracks only the optional-record bug.
A top-level tuple field written by uproot does not read back as a tuple. Reproduces on current main (a5f44fd) with plain uproot.recreate / uproot.open.
import awkward as ak, uproot
arr = ak.Array([(1.0, 2), (3.0, 4)]) # 2 * (float64, int64)
with uproot.recreate("tup.root") as f:
f["t"] = arr
with uproot.open("tup.root") as f:
form, _ = f["t"].to_akform()
print(form.is_tuple) # False, expected True
The tuple comes back as a record whose field names are the strings '0' and '1'.
Why
Tuple recovery exists and works — the reader restores tuple-ness when every on-disk member name matches _[0-9]+:
https://github.com/scikit-hep/uproot5/blob/main/src/uproot/models/RNTuple.py#L693
if all(re.fullmatch(r"_[0-9]+", name) is not None for name in record_names):
namelist = None # namelist=None -> RecordForm is a tuple
(and the same idea at models/RNTuple.py#L635 for the collection role).
The writer applies that naming convention for nested records but not at the top level:
# _cascadentuple.py#L355 — nested
subfield_name = f"_{i}" if akform.is_tuple else akform.fields[i]
# _cascadentuple.py#L439 — top level (generate_field_col_records)
for field_name, topakform in zip(akform.fields, akform.contents, strict=True):
akform.fields on a tuple is ['0', '1'] — no underscore — so the reader's heuristic misses. Measured:
top-level ak.Array([(1.0, 2), (3.0, 4)])
on-disk names: ['0', '1'] is_tuple: written True -> read False
nested ak.Array({"pair": [(1.0, 2), (3.0, 4)]})
on-disk names: ['pair', '_0', '_1'] is_tuple: written True -> read True
Nested tuples round-trip correctly; only the top level is affected.
Possible direction
Apply the same f"_{i}" if akform.is_tuple rule in generate_field_col_records. Note this changes on-disk field names for top-level tuples, so it is a format-visible change rather than a pure bug fix.
Note
This surfaced while reviewing #1687, whose extend() form check is the first code to compare a form reloaded from disk against the original. That check refuses this shape cleanly before writing anything, so #1687 is not affected — the mismatch is pre-existing and independent of it.
Environment
- uproot
5.7.6.dev29+g425d57430 (main @ a5f44fd)
- awkward
2.11.0
🤖 AI text below 🤖
Split out of #1713, which now tracks only the optional-record bug.
A top-level tuple field written by uproot does not read back as a tuple. Reproduces on current
main(a5f44fd) with plainuproot.recreate/uproot.open.The tuple comes back as a record whose field names are the strings
'0'and'1'.Why
Tuple recovery exists and works — the reader restores tuple-ness when every on-disk member name matches
_[0-9]+:https://github.com/scikit-hep/uproot5/blob/main/src/uproot/models/RNTuple.py#L693
(and the same idea at
models/RNTuple.py#L635for the collection role).The writer applies that naming convention for nested records but not at the top level:
akform.fieldson a tuple is['0', '1']— no underscore — so the reader's heuristic misses. Measured:Nested tuples round-trip correctly; only the top level is affected.
Possible direction
Apply the same
f"_{i}" if akform.is_tuplerule ingenerate_field_col_records. Note this changes on-disk field names for top-level tuples, so it is a format-visible change rather than a pure bug fix.Note
This surfaced while reviewing #1687, whose
extend()form check is the first code to compare a form reloaded from disk against the original. That check refuses this shape cleanly before writing anything, so #1687 is not affected — the mismatch is pre-existing and independent of it.Environment
5.7.6.dev29+g425d57430(main@a5f44fd)2.11.0