-
Notifications
You must be signed in to change notification settings - Fork 2
Integration with Uproot #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dbizdfvy
wants to merge
13
commits into
nsmith-:main
Choose a base branch
from
dbizdfvy:uproot-integration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c800239
Double32 format support in progress
dbizdfvy ff70cfe
Initial commit
dbizdfvy 6b9609b
Implement create_adapter_class
dbizdfvy 75eb3ce
bug fixes regarding create_adapter_class and added methods and proper…
dbizdfvy cac028a
Adapting rootfilespec values to uproot format
dbizdfvy c31aa45
bugfixes
dbizdfvy 25b0f23
Behavior inheritance temporary fix
dbizdfvy 66e316b
git
dbizdfvy 3c9b91b
Merge branch 'main' into uproot-integration
dbizdfvy caceb7a
Merge branch 'main' of github.com:nsmith-/rootfilespec into uproot-in…
nsmith- 426dd4b
Remove uv.lock
nsmith- faaaa67
Undo change in container
nsmith- 4a3e6b7
lint
nsmith- File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,8 +159,5 @@ Thumbs.db | |
| *.swp | ||
| .vscode | ||
|
|
||
| debug.py | ||
|
|
||
| # uv | ||
|
|
||
| uv.lock | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| from typing import Any | ||
|
|
||
| from uproot.model import Model # type: ignore[import-not-found] | ||
|
|
||
| from rootfilespec.bootstrap.strings import TString | ||
| from rootfilespec.serializable import ROOTSerializable, _get_annotations | ||
|
|
||
|
|
||
| class UprootModelAdapter(Model): # type: ignore[misc] | ||
| """ | ||
| Adapter for Uproot models to be used with the rootfilespec library. | ||
| This class allows Uproot to read ROOTSerializable objects | ||
| """ | ||
|
|
||
| _model: ROOTSerializable | ||
| _file: Any | ||
|
|
||
| def __init__(self, model: ROOTSerializable) -> None: | ||
| self._model = model | ||
|
|
||
| def __getattr__(self, name: str): | ||
| # Try the wrapped model first | ||
| if hasattr(self._model, name): | ||
| return getattr(self._model, name) | ||
|
|
||
| # Then try the class we're adapting to (Uproot behavior/model internals) | ||
| behavior_cls = getattr(type(self), "__behavior_cls__", None) | ||
| if behavior_cls and hasattr(behavior_cls, name): | ||
| return getattr(behavior_cls, name) | ||
|
|
||
| # Fall back to error | ||
| msg = f"{self.__class__.__name__} has no attribute {name!r}" | ||
| raise AttributeError(msg) | ||
|
|
||
| @property | ||
| def _fields(self): | ||
| return _get_annotations(type(self._model)) | ||
|
|
||
| @property | ||
| def encoded_classname(self): | ||
| name = type(self._model).__name__.replace("3a3a", "_3a3a_") | ||
| return "Model_" + name | ||
|
|
||
| def num_members(self): | ||
| return len(self._fields) | ||
|
|
||
| def member_name(self, index): | ||
| try: | ||
| return list(self._fields.keys())[index] | ||
| except IndexError: | ||
| err = f"Member index {index} out of range" | ||
| raise IndexError(err) from None | ||
|
|
||
| def member(self, name, all: bool = True, none_if_missing: bool = False): | ||
| if all: | ||
| value = getattr(self._model, name, None) | ||
| else: | ||
| # Check only declared fields on this type (not bases) | ||
| fields = _get_annotations(type(self._model)) | ||
| value = getattr(self._model, name, None) if name in fields else None | ||
|
|
||
| if none_if_missing and value is None: | ||
| msg = f"Member {name} not found in {self._model.__class__.__name__}" | ||
| raise AttributeError(msg) | ||
|
|
||
| return self._adapt_value(value) | ||
|
|
||
| def _adapt_value(self, value: Any) -> Any: | ||
| """Normalize ROOTSerializable values into Uproot-friendly objects.""" | ||
| if isinstance(value, ROOTSerializable): | ||
| if isinstance(value, TString): | ||
| return value.fString.decode("utf-8") | ||
|
|
||
| adapter_cls = create_adapter_class(type(value)) | ||
| return adapter_cls(value) | ||
|
|
||
| if isinstance(value, list | tuple): | ||
| return type(value)(self._adapt_value(v) for v in value) | ||
|
|
||
| if isinstance(value, dict): | ||
| return {k: self._adapt_value(v) for k, v in value.items()} | ||
|
|
||
| return value | ||
|
|
||
|
|
||
| def create_adapter_class(model_cls: type) -> type: | ||
| """ | ||
| Wrap a Model class (like Model_TTree_v20) into an Adapter that exposes | ||
| lookup, bases, cache_key, and underscored aliases safely. | ||
| """ | ||
| try: | ||
| behavior_subclasses = model_cls.behavior # type: ignore[attr-defined] | ||
| except AttributeError: | ||
| behavior_subclasses = ( | ||
| base for base in model_cls.__bases__ if "uproot.behavior" in base.__module__ | ||
| ) | ||
|
|
||
| # TODO: Check if we can use model_cls instead of *behavior_subclasses | ||
| class Adapter(UprootModelAdapter, *behavior_subclasses): # type: ignore[misc] | ||
| def __init__(self, model_instance): | ||
| super().__init__(model_instance) | ||
| self._internal_lookup = getattr(model_instance, "_lookup", {}) | ||
| self._internal_bases = getattr(model_instance, "_bases", []) | ||
| self._internal_cache_key = getattr(model_instance, "cache_key", None) | ||
|
|
||
| @property | ||
| def name(self): | ||
| return getattr(self._model, "name", None) | ||
|
|
||
| @property | ||
| def class_version(self): | ||
| return getattr(self._model, "class_version", None) | ||
|
|
||
| def __enter__(self): | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc, tb): | ||
| try: | ||
| close = getattr(self, "close", None) | ||
| if callable(close): | ||
| close() | ||
| else: | ||
| mclose = getattr(self._model, "close", None) | ||
| if callable(mclose): | ||
| mclose() | ||
| except Exception: | ||
| return False | ||
| return False | ||
|
|
||
| # def __getitem__(self, key): | ||
| # try: | ||
| # return self.lookup[key] | ||
| # except Exception: | ||
| # return self._internal_lookup[key] | ||
|
|
||
| # @property | ||
| # def lookup(self): | ||
| # val = getattr(self.model, "lookup", None) | ||
| # if val is None or isinstance(val, property): | ||
| # return self._internal_lookup | ||
| # return val | ||
|
|
||
| @property | ||
| def bases(self): | ||
| val = getattr(self.model, "bases", None) | ||
| if val is None or isinstance(val, property): | ||
| return self._internal_bases | ||
| return val | ||
|
|
||
| @property | ||
| def cache_key(self): | ||
| val = getattr(self.model, "cache_key", None) | ||
| if val is None or isinstance(val, property): | ||
| return self._internal_cache_key | ||
| return val | ||
|
|
||
| # @property | ||
| # def _lookup(self): | ||
| # return self.lookup | ||
|
|
||
| @property | ||
| def _bases(self): | ||
| return self.bases | ||
|
|
||
| @property | ||
| def _cache_key(self): | ||
| return self.cache_key | ||
|
|
||
| Adapter.__name__ = f"Adapter_{model_cls.__name__}" | ||
|
|
||
| return Adapter | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this context,
type(value)is a rootfilespec type butcreate_adapter_classexpects the uproot model. We need instead to find the appropriate class type of a member in uproot and then use that to wrap the rootfilespec type.