Repository navigation
Finish the Locator migration: no library code fetches (#114), and keep unknown locator types (#122) - #151
Merged
Conversation
TKey.read_from and TypedTKey.read_from read the key's object from the buffer they are given, through _read_payload, instead of passing read_object a lambda that returns it. read_object becomes a wrapper that fetches the key's bytes and calls the same code, until it is removed with the rest of the DataFetcher interface (nsmith-#114). No behaviour change, except that read_object on an RBlob key now fetches its bytes before refusing it. Assisted-by: claude-code:claude-opus-5-5
Before ROOTFile.get_TFile, get_StreamerInfo and TDirectory.get_KeyList go (nsmith-#114), compare their checks with their locators'. TFileLocator and StreamerInfoLocator already make every check theirs do. KeyListLocator lacked one: the key at fSeekKeys must have fNbytes == fNbytesKeys, the size the directory records for its key list. It now raises otherwise. tests/test_record_locators.py pins each check of the three locators on rntuple/anchor.root; test_size_mismatch_raises[keylist] fails on main. Assisted-by: claude-code:claude-opus-5-5
The last test on the DataFetcher interface (get_TFile, get_KeyList, read_object) reads through rootfilespec.reader instead, so that interface can go (nsmith-#114). It keeps to the bootstrap classes, now as a Fetcher on BOOTSTRAP_CONTEXT: the path for files whose StreamerInfo can't be turned into classes, such as the CMS MiniAOD files (nsmith-#41). Its assertions are unchanged. Assisted-by: claude-code:claude-opus-5-5
ROOTFile.get_TFile, ROOTFile.get_StreamerInfo, TFile.get_KeyList,
TDirectory.get_KeyList, TKey.read_object and the DataFetcher alias fetched
inside library code, which docs/design.md rules out ("objects describe where
data is; callers decide when and how to fetch it"). Each has a locator that
does the same without fetching, with the same checks (previous commit):
- get_TFile: fetch.resolve(file.tfile_locator)
- get_StreamerInfo: fetch.resolve(file.streamerinfo_locator)
- get_KeyList: fetch.resolve(directory.keylist_locator), or reader.keylist()
- key.read_object(fetch_data): fetch(key), or key.read_from(buffer)
- key.read_object(fetch_data, cls): fetch(TypedTKey(key, cls))
The tests that called read_object call read_from on the same bytes.
Part of nsmith-#114.
Assisted-by: claude-code:claude-opus-5-5
RNTuple.from_anchor fetched, through a locator-to-buffer callable, and so did the methods it called: ROOT3a3aRNTuple.get_header / get_footer, REnvelopeLink.read_envelope, FooterEnvelope.get_pagelists. So did PageListEnvelope.get_pages and RPageDescription.get_page. docs/design.md rules that out, and Nick asked for the whole middle generation to go (nsmith-#114). All are removed; every reference in the chain is reached through its locator: anchor -> header_locator / footer_locator -> footer.pagelist_locators -> page list. - RNTuple.from_envelopes(header, footer, pagelists) does no I/O and keeps from_anchor's checks: the footer and each page list must record the header's checksum. - Fetcher.rntuple(anchor) fetches the envelopes one read at a time, for the synchronous case. It is on Fetcher rather than FileReader because an RNTuple needs only the bootstrap classes: a Fetcher on BOOTSTRAP_CONTEXT reads the RNTuples of files that FileReader.open can't open, such as the CMS MiniAOD files (nsmith-#41). From a FileReader: reader.fetch.rntuple(anchor). It takes the anchor, so finding the key stays with the caller. Tests: from_anchor calls become fetch.rntuple. New tests/test_rntuple_fetching.py: on every root-io-spec RNTuple fixture, the RNTuple built by hand from the locators equals Fetcher.rntuple's, and a bootstrap-only Fetcher reads the same RNTuples as open_path; from_envelopes refuses a footer or a page list of another header. New tests/test_no_fetch_callables.py: no public function or method under rootfilespec.bootstrap or rootfilespec.rntuple takes a callable or a fetch argument; on main it lists get_TFile, from_anchor and the others. Part of nsmith-#114. Assisted-by: claude-code:claude-opus-5-5
RPageDescription was its page's locator (nsmith-#128). As Nick set out approving nsmith-#128, pages now follow the envelope pattern (REnvelopeLink and REnvelopeLocator): RPageDescription is the page-list record only, and loses offset, size and read_from, so there is one way to read a page. Its page_locator returns an RPageLocator, which follows the Locator protocol and, like REnvelopeLocator, keeps the RLocator instead of copying out integers. Its size covers the checksum trailer when the page has one, and its read_from verifies it (moved from RPageDescription.read_from). page_locator covers the locators of a byte range in the file, StandardLocator and LargeLocator (FileLocator), and raises NotImplementedError for any other: the Locator protocol is an offset and a size in the file. PageListEnvelope.page_locators returns RPageLocators. InterpretablePage.pageDescription (nsmith-#131) stays the record; its docstring now says the bytes come from page_locator. Tests: tests/test_page_checksums.py reads pages through page_locator, with the same assertions on rntuple/anchor.root's page at 550 and on every fixture's pages. Part of nsmith-#114. Assisted-by: claude-code:claude-opus-5-5
… ROOT does RLocator.read raised on any non-standard locator type but 0x01, so one page with such a locator made its whole page list unreadable. ROOT reads a type it doesn't know as kTypeUnknown, and only fails when something reads what it locates (RNTupleSerializer::DeserializeLocator, tree/ntuple/src/RNTupleSerialize.cxx:1112-1155 at 6.40.04). rootfilespec now does the same (nsmith-#122): such a locator reads as an UnknownLocator, keeping its type, reserved byte and payload as stored, and page_locator and envelope_locator raise NotImplementedError on it. That includes 0x02, ROOT's DAOS locator (root-io-spec ERRATA 4), which the release doesn't need; reading it is nsmith-#148. Finding the payload needs the locator's own size, and that showed the header word was decoded wrongly. ROOT writes it as the negation of (type << 24 | reserved << 16 | size), with the size counting the word itself, and negates it back before splitting it (:1104-1107, :1124-1127). rootfilespec took the type from the top byte of the negated word, so a large locator as ROOT writes it read as type 0xfe and raised. No fixture has one: ROOT writes them only for blocks over 2 GiB. The spec's "absolute value of the 8bit integer" agrees with ROOT only when the low 24 bits are zero, which they never are. - RLocator no longer has a size: an unknown locator has none. StandardLocator and LargeLocator have their own, in the same place. - LargeLocator keeps its reserved byte (default 0); a payload other than 16 bytes raises. LargeLocator.read is replaced by LargeLocator.from_payload. - REnvelopeLocator.locator is a FileLocator, so offset needs no cast. Tests: tests/test_unknown_locators.py. A large locator as ROOT writes it reads (fails on main: type 0xfe); types 0x7e, 0x02 (both Object64 payload sizes) and an unused type are kept with their payload and reserved byte; rntuple/anchor.root with its page locator at 724 replaced by a 0x7e locator of the same length and the envelope checksum recomputed reads, and only that page's page_locator raises (fails on main: the page list raises). Assisted-by: claude-code:claude-opus-5-5
The Locator Pattern gains the RNTuple chain (anchor -> header_locator / footer_locator -> pagelist_locators -> page_locator), the record/locator split pages now share with envelopes, which locators can be fetched, and RNTuple.from_envelopes. The reader section gains an RNTuple example, and the bootstrap-only path for files whose StreamerInfo can't be turned into classes (nsmith-#41). The sentence about the callable RNTuple.from_anchor expected goes with from_anchor. Both examples were run on rntuple/anchor.root. Part of nsmith-#114. Assisted-by: claude-code:claude-opus-5-5
…smith-#150) into fix/114-locator-migration Conflicts resolved and their changes ported to this branch's API: - N (nsmith-#147): TKey._read_payload without objtype and TKey.read_from return object, as N types read_object and read_from; N's unbound T_co and its comment kept, without DataFetcher. - M (nsmith-#146): pagelist.py keeps M's Members / ReadBuffer imports for ClusterSummary.update_members; tests/test_sharded_clusters.py uses reader.fetch.rntuple(anchor). - O (nsmith-#145): O's key lookups and isinstance narrowing kept in the tests both sides edited, with from_anchor ported to fetch.rntuple; test_read_multiple_rntuples and the two docs/design.md RNTuple examples use keylist.get_by_name. - P (nsmith-#150): tests/test_frame_bounds.py uses reader.fetch.rntuple(anchor). 631 passed, 59 skipped, 49 xfailed on Python 3.13, 3.14 and 3.10; pre-commit clean. Assisted-by: claude-code:claude-opus-5-5
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #151 +/- ##
==========================================
+ Coverage 93.36% 95.13% +1.77%
==========================================
Files 37 37
Lines 3299 3269 -30
==========================================
+ Hits 3080 3110 +30
+ Misses 219 159 -60 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… types Which class a locator is depends on its first word, so LargeLocator.read and UnknownLocator.read, inherited from RLocator.read, could return another class (a StandardLocator, say), unlike every other X.read in rootfilespec. They now raise TypeError pointing to RLocator.read, which nothing in rootfilespec bypasses. StandardLocator.read is unchanged. tests/test_no_fetch_callables.py types its helpers with ModuleType and Callable[..., object] instead of object, which drops its type: ignore; test_rntuple_fetching.py annotates the key list. New: test_only_rlocator_reads_a_locator. Part of nsmith-#114. Assisted-by: claude-code:claude-opus-5-5
Collaborator
Author
|
@nsmith- ok I have a decent understanding of this, and I think it is ready for you! |
nsmith-
previously requested changes
Oct 6, 2026
nsmith-
left a comment
Owner
There was a problem hiding this comment.
Two changes, one item to file for later work
docs/design.md gains a batch example for the RNTuple case, as Nick asked on nsmith-#151: with an asynchronous fetch_data, the header and the footer are fetched in parallel, then every page list, and RNTuple.from_envelopes assembles them. Run on every root-io-spec RNTuple fixture, it gives the RNTuple that Fetcher.rntuple gives. tests/test_no_fetch_callables.py goes (Nick: not needed). Part of nsmith-#114. Assisted-by: claude-code:claude-opus-5-5
sathabbott
added a commit
that referenced
this pull request
Oct 6, 2026
…51) (#156) * Read the footer's list of linked attribute sets The footer ends with a list frame of linked attribute set records (spec, "Footer Envelope", "Linked Attribute Set Record Frame"), which rootfilespec left in the footer's _unknown: the empty list of every footer of format 1.0.1.0 or later, and both records of attributes.root. - LinkedAttributeSet, a record frame: fSchemaVersionMajor and fSchemaVersionMinor (u16), fAnchorLength (u32: the uncompressed length of the set's anchor, 78 bytes for class version 2, root-io-spec RNTuple ERRATA 12), locator (the anchor object's RLocator), fName (an RNTuple string). Field for field as RNTupleSerializer writes and reads it (RNTupleSerialize.cxx:1843-1866, :2045-2078 at 6.40.04). - FooterEnvelope.attributeSets, a ListFrame of them, or None when the footer ends before it: the list arrived with format 1.0.1.0, and ROOT reads it only if bytes remain before the checksum (ERRATA 11; RNTupleSerialize.cxx:2015-2017). REnvelope.read already hands update_members a payload that ends before the checksum (#129), so the remaining payload length #51 asks for is the length of that buffer. - structutil.IfBytesRemain: Annotated[T | None, IfBytesRemain()] reads T if bytes remain and gives None otherwise. Tests (tests/test_attribute_sets.py): attributes.root's two records against its case.toml; every root-io-spec RNTuple fixture's footer has the list, and nothing in _unknown; the two 1.0.0.0 files of scikit-hep-testdata have no list and still read. The hardcoded footers of those two files gain attributeSets=None. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * Open an attribute set through its footer record: anchor_locator An attribute set's anchor is a ROOT::RNTuple key that no directory lists: the footer record's locator, which names the anchor object rather than its key, is the only way to it (root-io-spec RNTuple NOTES 8, ERRATA 12). LinkedAttributeSet.anchor_locator returns a locator of that anchor, read with ROOT3a3aRNTuple.read (#85's checksum and length rules), so the set opens like any RNTuple: fetch.rntuple(fetch(record.anchor_locator)). ROOT opens the anchor from the record as an RNTupleLink, the link of an uncompressed length and a locator that envelopes use, and decompresses it the same way (RPageSourceFile::OpenWithDifferentAnchor, RPageStorageFile.cxx:369-376 at 6.40.04). So anchor_locator is an REnvelopeLocator, whose type parameter now allows any ROOTSerializable rather than only an REnvelope. Like envelope_locator, it raises NotImplementedError if the locator is not in the file. Tests: attributes.root's two sets open through their records, at the anchor offsets, header and footer positions the case.toml pins; each is an RNTuple named after its set, with schema 1.0's three top-level fields and an empty attribute set list of its own; the _rangeStart and _rangeLen pages read (0, 3), (3, 3) for "runs" and (3, 0), (1, 4) for "flags". A record whose locator is not in the file raises. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * Check attribute sets as ROOT does: names, schema version, meta-fields What ROOT refuses about attribute sets, and where: - Reading the main footer, a set without a name, or two of one name (RNTupleAttrSetDescriptorBuilder::MoveDescriptor and RNTupleDescriptorBuilder::AddAttributeSet, RNTupleDescriptor.cxx:1156-1159, :1438-1446 at 6.40.04; spec, "Linked Attribute Sets": "All linked attribute sets must have a non-empty, distinct name"). Now RNTuple.from_envelopes raises ValueError. - Opening a set (RNTupleAttrSetReader, RNTupleAttrReading.cxx:20-46): a major schema version other than 1, which the spec says a reader refuses ("Attribute Schema Version"); and top-level fields other than _rangeStart, _rangeLen and _userData, in that order. That includes a fourth field, which ROOT refuses whatever the minor version, though the spec says a later minor version's fields are ignored (root-io-spec RNTuple ERRATA 13). Now LinkedAttributeSet.check(rntuple) raises NotImplementedError for the major version and ValueError for the fields. check also tests the spec's three restrictions on an attribute set's RNTuple, which ROOT's writer keeps and its reader doesn't check (NOTES 8): no attribute sets of its own, no alias columns, no field of structural role 0x04. Fetcher.attribute_set(record) opens a set as ROOT's OpenAttributeSet does: fetch.rntuple of the record's anchor, then record.check. fetch.rntuple(fetch(record.anchor_locator)) still reads any set as an RNTuple. Tests: both sets of attributes.root open with Fetcher.attribute_set; with the "runs" record's major version patched to 2 (footer checksum recomputed), the main RNTuple reads and "flags" opens but "runs" raises, as ERRATA 13 found ROOT does. On copies of "runs": a fourth top-level field (minor version 0 and 1), a renamed meta-field, and each restriction raise. A footer with a duplicate or empty set name raises in from_envelopes. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * docs/design.md: attribute sets in the RNTuple locator chain Each linked attribute set record has an anchor_locator, and Fetcher.attribute_set opens the set, in the RNTuple example. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * Check attribute sets only as ROOT does: drop the spec's restrictions LinkedAttributeSet.check no longer refuses a set that links sets of its own, has alias columns, or has a field of structural role 0x04. ROOT's writer never writes such a set, but its reader opens one without complaint (root-io-spec RNTuple NOTES 8: the set reader checks only the major version and the three meta-fields, RNTupleAttrReading.cxx:20-46 at 6.40.04), and rootfilespec aims at ROOT's strictness. check keeps those two tests. test_restrictions becomes test_restrictions_are_not_checked: a copy of "runs" breaking all three passes. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * A class of its own for linked anchors: RAnchorLocator LinkedAttributeSet.anchor_locator returned an REnvelopeLocator of ROOT3a3aRNTuple, with REnvelopeLocator's type parameter widened from REnvelope to any ROOTSerializable. An anchor isn't an envelope, so it now has its own locator, RAnchorLocator (uncompressed length and FileLocator, read with ROOT3a3aRNTuple.read), and REnvelopeLocator is as #151 left it. The two share what ROOT does for both, as it opens a linked anchor as an RNTupleLink, the link envelopes use (RPageStorageFile.cxx:369-376 at 6.40.04): check the fetched size against the locator's, and decompress when the stored size is below the length (root-io-spec RNTuple NOTES 2). That moves from REnvelopeLocator.read_from to the helper _uncompressed, with the same errors. Tests: anchor_locator is an RAnchorLocator; "runs"' anchor in a zlib block reads as the raw one does; a short fetch raises. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * Refuse the attribute set records ROOT refuses as it reads the footer ROOT refuses the whole footer, so the main RNTuple, for an attribute set record with an anchor length of 0, or with an anchor locator of a type it doesn't know: any non-standard type but 0x01 and 0x02, which DeserializeLocator reads as kTypeUnknown (RNTupleAttrSetDescriptorBuilder::MoveDescriptor, RNTupleDescriptor.cxx: 1156-1163; RNTupleSerialize.cxx:1131-1141 at 6.40.04). rootfilespec read such a footer and failed only when the set was opened. RNTuple.from_envelopes now refuses both, with the empty and repeated names it already refused, in one function, _check_attribute_sets. A DAOS (0x02) anchor locator is a type ROOT knows: the footer reads, and anchor_locator raises when the set is opened, as before. Tests: from_envelopes raises for an anchor length of 0 and for anchor locators of types 0x7e (ROOT's test type) and 0x40; a 0x02 locator reads, and only its anchor_locator raises. The names test uses the same helper. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * docs/design.md: RAnchorLocator among the records and their locators Part of #51. Assisted-by: claude-code:claude-opus-5-5 * docs/design.md: attribute sets in the RNTuple batch-fetching example Their anchors are known once the footer is, so they can be fetched in parallel with the page lists, and each set read the same way. Part of #51. Assisted-by: claude-code:claude-opus-5-5 * docs/design.md: keep RNTuple details out, as on main Nick on #156: the file should stay scoped to the design philosophy, with only a few examples, not details of the RNTuple structure. F's additions (attribute sets in the RNTuple locator chain, RAnchorLocator among the records and their locators, and attribute sets after the batch-fetching example) go; the file is as on main. Attribute sets are documented in the docstrings of LinkedAttributeSet, RAnchorLocator and Fetcher.attribute_set. Part of #51. Assisted-by: claude-code:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #114. Fixes #122. Fixes #149.
No library object fetches data any more: both older generations of "get me the object" go, and every reference is reached through a
*_locator, asdocs/design.mdasks ("objects describe where data is; callers decide when and how to fetch it"). The plan was posted on #114 (2026-09-24, updated 2026-10-05).What changed
The DataFetcher interface goes (
ROOTFile.get_TFile,get_StreamerInfo,TFile.get_KeyList,TDirectory.get_KeyList,TKey.read_object,DataFetcher).TKey.read_from(buffer)andTypedTKey.read_from(buffer)are now the implementation, with no behaviour change. Before removing theget_*methods I compared their checks with their locators':KeyListLocatorlacked one, that the key'sfNbytesequals the directory'sfNbytesKeys, and now has it.The middle generation goes (
RNTuple.from_anchor,ROOT3a3aRNTuple.get_header/get_footer,REnvelopeLink.read_envelope,FooterEnvelope.get_pagelists,PageListEnvelope.get_pages,RPageDescription.get_page). The chain is anchor →header_locator/footer_locator→ footer'spagelist_locators→ each page description'spage_locator.RNTuple.from_envelopes(header, footer, pagelists)does no I/O and keepsfrom_anchor's checks: the footer and every page list must record the header's checksum.Fetcher.rntuple(anchor)fetches the envelopes one read at a time; from aFileReader,reader.fetch.rntuple(anchor). It is onFetcherbecause an RNTuple needs only the bootstrap classes, so it works on aFetcheronBOOTSTRAP_CONTEXT. That is the only way to read the RNTuples of files whose StreamerInfo can't be turned into classes:FileReader.openfails on the CMS MiniAOD files (Implement double32 type #41). It takes the anchor, so finding the key stays with the caller.Pages follow the envelope pattern, as set out approving #128.
RPageDescriptionis the page-list record only, likeREnvelopeLink, and losesoffset,sizeandread_from. Itspage_locatorreturns anRPageLocator, which follows theLocatorprotocol and, likeREnvelopeLocator, keeps theRLocator. Itssizecovers the checksum trailer when there is one, and itsread_fromverifies it (moved from #128'sRPageDescription.read_from).PageListEnvelope.page_locatorsreturnsRPageLocators. #131'sInterpretablePage.pageDescriptionstays the record; its docstring says the bytes come frompage_locator.Unknown locator types are kept (#122).
RLocator.readraised on any non-standard type but0x01, so one such page made its whole page list unreadable. ROOT reads an unknown type askTypeUnknownand fails only when reading what it locates (RNTupleSerializer::DeserializeLocator,tree/ntuple/src/RNTupleSerialize.cxx:1112-1155at 6.40.04). Now such a locator reads as anUnknownLocatorwith its type, reserved byte and payload as stored, andpage_locator/envelope_locatorraiseNotImplementedErroron it. That includes0x02, ROOT's DAOS locator (ERRATA 4, "Locator type0x02is assigned and implemented, not reserved"); reading it is #148. OnlyStandardLocatorandLargeLocator(FileLocator) can be fetched.A large locator as ROOT writes it now reads (#149, found while doing #122). ROOT writes a non-standard locator's first word as the negation of
(type << 24 | reserved << 16 | size), where the size counts the word itself, and negates it back before splitting it (:1104-1107,:1124-1127). rootfilespec took the type from the top byte of the negated word, so a large locator read as type0xfeand raised. No fixture has one: ROOT writes them only for blocks over 2 GiB. (The spec's "the absolute value of the 8bit integer" (BinaryFormatSpecification, "Locators and Envelope Links") gives the type only when the low 24 bits are zero, and they hold the size: filed as ariostas/root-io-spec#5.)docs/design.md: the RNTuple locator chain, the record/locator split, an RNTuple example, a batch example that fetches the header and footer in parallel and then the page lists, and the bootstrap-only path.Tests
test_record_locators.py: every check the removedget_*methods made, on the locators, onrntuple/anchor.root.test_size_mismatch_raises[keylist]fails onmain.test_rntuple_fetching.py: on every root-io-spec RNTuple fixture, the RNTuple built by hand from the locators andfrom_envelopesequalsFetcher.rntuple's, and aFetcheronBOOTSTRAP_CONTEXTreads the same RNTuples asopen_path.from_envelopesrefuses a footer, and a page list, recording another header's checksum. All fail onmain(nofrom_envelopes, noFetcher.rntuple).test_unknown_locators.py: a large locator as ROOT writes it reads (fails onmain: type0xfe). Types0x7e,0x02(both Object64 payload sizes) and an unused one keep their payload and reserved byte.rntuple/anchor.rootwith its page locator at 724 (in the page list's RBlob record, whichcase.tomlplaces at 590) replaced by a0x7elocator of the same length (envelope checksum recomputed) reads, and only that page'spage_locatorraises (fails onmain: the page list raises).test_read_multiple_rntuples(the last test on the DataFetcher interface, now aFetcheronBOOTSTRAP_CONTEXT), everyfrom_anchorcall (→fetch.rntuple, including those in [rntuple] Refuse a cluster summary with the sharded-cluster flag #146'stest_sharded_clusters.pyand [rntuple] Read a frame's members from its fSize bytes only #150'stest_frame_bounds.py),read_objectcalls (→read_from), andtest_page_checksums.py(→page_locator).Full suite: 631 passed, 59 skipped, 49 xfailed, on Python 3.13.
pre-commit run --all-filespasses.Breaking changes
ROOTFile.get_TFile(fetch_data)fetch.resolve(file.tfile_locator)ROOTFile.get_StreamerInfo(fetch_data)fetch.resolve(file.streamerinfo_locator)TFile.get_KeyList(fetch_data),TDirectory.get_KeyList(fetch_data)fetch.resolve(directory.keylist_locator), orreader.keylist(directory)TKey.read_object(fetch_data)fetch(key), orkey.read_from(buffer): typedobjectsince #147, so narrow withisinstanceTKey.read_object(fetch_data, cls)fetch(TypedTKey(key, cls))rootfilespec.serializable.DataFetcherFetcher, or your own fetching of(offset, size)RNTuple.from_anchor(anchor, fetch.buffer)fetch.rntuple(anchor), orRNTuple.from_envelopes(header, footer, pagelists)ROOT3a3aRNTuple.get_header/get_footerfetch(anchor.header_locator)/fetch(anchor.footer_locator)REnvelopeLink.read_envelope(fetch_data, envtype)fetch(link.envelope_locator(envtype))FooterEnvelope.get_pagelists(fetch_data)[fetch(loc) for loc in footer.pagelist_locators]PageListEnvelope.get_pages(fetch_data)fetch(loc)for each ofpagelist.page_locatorsRPageDescription.get_page,.read_from,.offset,.sizepage.page_locator(anRPageLocator):fetch(page.page_locator),.offset,.sizePageListEnvelope.page_locatorsgaveRPageDescriptionsRPageLocators; the records are inpageLocationsRLocator.sizeStandardLocatorandLargeLocator; anUnknownLocatorhas noneLargeLocator.read(buffer)(read the payload)RLocator.read(buffer)for a whole locator;LargeLocator.from_payload(payload, reserved).LargeLocator.readandUnknownLocator.readraiseTypeError: which class a locator is depends on its first wordRLocator.readraisedValueErroron an unknown non-standard typeUnknownLocator;page_locator/envelope_locatorraiseNotImplementedErroron itKeyListLocator.read_fromaccepted a key whosefNbytesdiffers from the locator's sizeValueErrorAssisted-by: claude-code:claude-opus-5-5