Repository navigation
FileContext.type_by_name returns object: a lookup can be an annotated builtin - #147
Merged
Merged
Conversation
… builtin type_by_name was annotated -> type[ROOTSerializable], but TString, TStringLong and string resolve to annotated bytes aliases (nsmith-#68), and a file's context resolves TDatime to an annotated int. mypy therefore let callers call .read() on the result, the TypedTKey bug nsmith-#127 fixed, and test_read.py's basket reader still did. Option 1 on nsmith-#135: FileContext.type_by_name and both implementations return object, with the contract in the docstring: read the result with read_value, which now takes object, and narrow with isinstance(t, type) and issubclass(t, ROOTSerializable) where the class is needed. mypy now rejects .read() on a lookup. TKey.read_object (untyped) and TKey.read_from return object as well, since a TString record reads as bytes; so the Locator protocol's type variable is no longer bound to ROOTSerializable. The basket reader in test_read.py uses read_value. Fixes nsmith-#135 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 #147 +/- ##
=======================================
Coverage 93.31% 93.31%
=======================================
Files 37 37
Lines 3277 3277
=======================================
Hits 3058 3058
Misses 219 219 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
sathabbott
commented
Oct 5, 2026
Collaborator
Author
|
@nsmith- this is ready for you, but I don't fully understand it. |
nsmith-#145 typed two string-record reads in test_root_string.py as object, because TKey.read_from claimed ROOTSerializable. It is typed object now, so the reads compare directly again. Assisted-by: claude-code:claude-opus-5-5
nsmith-
approved these changes
Oct 5, 2026
The comment on T_co only said why it is unbound. It is the type a locator returns (Locator[RPage], Locator[TTree]), unbound because some locators return a builtin. Wording from Nick's review of nsmith-#147. 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 #135.
FileContext.type_by_namewas annotated-> type[ROOTSerializable], but a lookup can return an annotated builtin.TString,TStringLongandstringresolve toAnnotated[bytes, ROOTString(...)](#127), and a file's context resolvesTDatimetoAnnotated[int, Fmt(">I")]. mypy therefore accepted.read()on the result, theTypedTKeybug #127 fixed.tests/test_read.py's basket reader still did it.As decided on the issue (option 1,
objectrather thanAny):FileContext.type_by_name,_BootstrapContext.type_by_nameandDynamicFileContext.type_by_namereturnobject. The base class's docstring states the contract: read the result withread_value, never.read(), and narrow withisinstance(t, type) and issubclass(t, ROOTSerializable)where the class itself is needed. mypy now rejects.read()on a lookup.read_valuetakesobject, so it accepts a lookup's result unchanged.tests/test_read.py's_ReadBasketreads its items withread_value.Beyond the issue, and why:
TKey.read_object(withoutobjtype) andTKey.read_fromreturnobject. They were annotatedROOTSerializable, but they return whatever the lookup reads:bytesfor aTStringrecord,intfor aTDatimeone. This is the same bug one level up, and TKeyList: key by (name, cycle), with get_by_name for the highest cycle #145 surfaced it once key lookups became typed. Withobjtypegiven,read_objectstill returns that type.Locatorprotocol's type variable is no longer bound toROOTSerializable. ATKeyis a locator, and its object can be a builtin, so the bound no longer holds.Fetcher.__call__then typesreader.fetch(key)asobject. Callers narrow withisinstance, which every caller in the tests already does where it needs a class. Remove the DataFetcher-era methods now that locators and rootfilespec.reader exist #114 reworks the locators; this changes only the bound.Not changed:
TypedTKey.objtypestaystype[ObjType].TypedTKey.readkeeps its# type: ignore[arg-type], since for a string recordobjtypeis the alias.type_by_checksumis not implemented anywhere and is left as it is.Tests
New
tests/test_type_by_name.py:test_return_type_is_object: the return annotation ofFileContext.type_by_name, of both implementations and ofTKey.read_fromisobject. All four fail onmain.test_lookup_of_tdatime_is_an_annotated_builtin, on root-io-spec'sserialization/unframed-records.root:TDatimeto the alias, which is not a class.read_valuereads the record's payload as0x7E6CC000, the value itscase.tomlpins at 318, with nothing left over.reader.fetch(key)returns the same value..read()raisesAttributeErrorat run time. The test calls it with# type: ignore[attr-defined], so CI's mypy pins the type statically: onmainthat ignore is unused and the line before it unreachable, and mypy fails.test_lookup_of_strings:TString,TStringLongandstringresolve to their aliases in the bootstrap context and in a file's.test_lookup_of_a_class_narrows:TNamednarrows to the class.Since #145 merged,
test_root_string.py's two string-record reads comparereader.fetch(...)withbytesdirectly again. #145 had typed themobjectto work aroundTKey.read_from's old annotation.Full suite:
577 passed, 59 skipped, 49 xfailed(mainat2a65519: 568 passed).Breaking changes
FileContext.type_by_name(and its implementations) returnsobject. Code that calls.read()on the result fails type checking; read withserializable.read_value(t, buffer), or narrow withisinstance(t, type) and issubclass(t, ROOTSerializable)first. Run-time behaviour is unchanged.TKey.read_fromandTKey.read_objectwithoutobjtypeare typedobject, soreader.fetch(key)is too. Narrow withisinstancebefore using the result as a particular class.serializable.Locator's type variable is unbound: a locator may return a builtin.Assisted-by: claude-code:claude-opus-5-5