-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[rfile] Add pythonizations #20167
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
base: master
Are you sure you want to change the base?
[rfile] Add pythonizations #20167
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| # Author: Giacomo Parolini CERN 04/2025 | ||
|
|
||
| r""" | ||
| \pythondoc RFile | ||
|
|
||
| TODO: document RFile | ||
|
|
||
| \code{.py} | ||
| # TODO code example | ||
| \endcode | ||
|
|
||
| \endpythondoc | ||
| """ | ||
|
|
||
| from . import pythonization | ||
|
|
||
|
|
||
| class _RFile_Get: | ||
| """ | ||
| Allow access to objects through the method Get(). | ||
| This is pythonized to allow Get() to be called both with and without a template argument. | ||
| """ | ||
|
|
||
| def __init__(self, rfile): | ||
| self._rfile = rfile | ||
|
|
||
| def __call__(self, namecycle): | ||
| """ | ||
| Non-templated Get() | ||
| """ | ||
| import ROOT | ||
| import cppyy | ||
|
|
||
| key = self._rfile.GetKeyInfo(namecycle) | ||
| if key: | ||
| obj = ROOT.Experimental.Internal.RFile_GetObjectFromKey(self._rfile, key) | ||
| return cppyy.bind_object(obj, key.GetClassName()) | ||
| # No key | ||
| return None | ||
|
|
||
| def __getitem__(self, template_arg): | ||
| """ | ||
| Templated Get() | ||
| """ | ||
|
|
||
| def getitem_wrapper(namecycle): | ||
| obj = self._rfile._OriginalGet[template_arg](namecycle) | ||
| return obj if obj else None | ||
|
|
||
| return getitem_wrapper | ||
|
|
||
|
|
||
| class _RFile_Put: | ||
| """ | ||
| Allow writing objects through the method Put(). | ||
| This is pythonized to allow Put() to be called both with and without a template argument. | ||
| """ | ||
|
|
||
| def __init__(self, rfile): | ||
| self._rfile = rfile | ||
|
|
||
| def __call__(self, name, obj): | ||
| """ | ||
| Non-templated Put() | ||
| """ | ||
| objType = type(obj) | ||
| if objType == str: | ||
|
Check failure on line 67 in bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py
|
||
| # special case: automatically convert python str to std::string | ||
| className = "std::string" | ||
| elif not hasattr(objType, '__cpp_name__'): | ||
| raise TypeError(f"type {objType} is not supported by ROOT I/O") | ||
| else: | ||
| className = objType.__cpp_name__ | ||
| self._rfile.Put[className](name, obj) | ||
|
|
||
| def __getitem__(self, template_arg): | ||
| """ | ||
| Templated Put() | ||
| """ | ||
| return self._rfile._OriginalPut[template_arg] | ||
|
|
||
|
|
||
| def _RFileExit(obj, exc_type, exc_val, exc_tb): | ||
| """ | ||
| Close the RFile object. | ||
| Signature and return value are imposed by Python, see | ||
| https://docs.python.org/3/library/stdtypes.html#typecontextmanager. | ||
| """ | ||
| obj.Close() | ||
| return False | ||
|
|
||
|
|
||
| def _RFileOpen(original): | ||
| """ | ||
| Pythonization for the factory methods (Recreate, Open, Update) | ||
| """ | ||
|
|
||
| def rfile_open_wrapper(klass, *args): | ||
| rfile = original(*args) | ||
| rfile._OriginalGet = rfile.Get | ||
| rfile.Get = _RFile_Get(rfile) | ||
| rfile._OriginalPut = rfile.Put | ||
| rfile.Put = _RFile_Put(rfile) | ||
| return rfile | ||
|
|
||
| return rfile_open_wrapper | ||
|
|
||
|
|
||
| def _RFileInit(rfile): | ||
| """ | ||
| Prevent the creation of RFile through constructor (must use a factory method) | ||
| """ | ||
| raise NotImplementedError("RFile can only be created via Recreate, Open or Update") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
|
|
||
|
|
||
| def _GetKeyInfo(rfile, path): | ||
| key = rfile._OriginalGetKeyInfo(path) | ||
| if key.has_value(): | ||
| return key.value() | ||
| return None | ||
|
|
||
|
|
||
| def _ListKeys(rfile, basePath="", listObjects = True, listDirs = False, listRecursive = True): | ||
| from ROOT.Experimental import RFile | ||
|
|
||
| flags = (listObjects * RFile.kListObjects) | (listDirs * RFile.kListDirs) | (listRecursive * RFile.kListRecursive) | ||
| iter = rfile._OriginalListKeys(basePath, flags) | ||
| return iter | ||
|
|
||
|
|
||
| @pythonization("RFile", ns="ROOT::Experimental") | ||
| def pythonize_rfile(klass): | ||
| # Explicitly prevent to create a RFile via ctor | ||
| klass.__init__ = _RFileInit | ||
|
|
||
| # Pythonize factory methods | ||
| klass.Open = classmethod(_RFileOpen(klass.Open)) | ||
| klass.Update = classmethod(_RFileOpen(klass.Update)) | ||
| klass.Recreate = classmethod(_RFileOpen(klass.Recreate)) | ||
|
|
||
| # Pythonization for __enter__ and __exit__ methods | ||
| # These make RFile usable in a `with` statement as a context manager | ||
| klass.__enter__ = lambda rfile: rfile | ||
| klass.__exit__ = _RFileExit | ||
| klass._OriginalGetKeyInfo = klass.GetKeyInfo | ||
| klass.GetKeyInfo = _GetKeyInfo | ||
| klass._OriginalListKeys = klass.ListKeys | ||
| klass.ListKeys = _ListKeys | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,10 +11,12 @@ | |||||||||||
| #include <ROOT/RError.hxx> | ||||||||||||
|
|
||||||||||||
| #include <deque> | ||||||||||||
| #include <memory> | ||||||||||||
| #include <functional> | ||||||||||||
| #include <iostream> | ||||||||||||
| #include <memory> | ||||||||||||
| #include <string_view> | ||||||||||||
| #include <typeinfo> | ||||||||||||
| #include <variant> | ||||||||||||
|
|
||||||||||||
| class TFile; | ||||||||||||
| class TIterator; | ||||||||||||
|
|
@@ -23,13 +25,17 @@ class TKey; | |||||||||||
| namespace ROOT { | ||||||||||||
| namespace Experimental { | ||||||||||||
|
|
||||||||||||
| class RKeyInfo; | ||||||||||||
| class RFile; | ||||||||||||
| struct RFileKeyInfo; | ||||||||||||
|
|
||||||||||||
| namespace Internal { | ||||||||||||
|
|
||||||||||||
| ROOT::RLogChannel &RFileLog(); | ||||||||||||
|
|
||||||||||||
| /// Returns an **owning** pointer to the object referenced by `key`. The caller must delete this pointer. | ||||||||||||
| /// This method is meant to only be used by the pythonization. | ||||||||||||
| [[nodiscard]] void *RFile_GetObjectFromKey(RFile &file, const RKeyInfo &key); | ||||||||||||
|
|
||||||||||||
| } // namespace Internal | ||||||||||||
|
|
||||||||||||
| namespace Detail { | ||||||||||||
|
|
@@ -59,6 +65,7 @@ Querying this information can be done via RFile::ListKeys(). Reading an object's | |||||||||||
| doesn't deserialize the full object, so it's a relatively lightweight operation. | ||||||||||||
| */ | ||||||||||||
| class RKeyInfo final { | ||||||||||||
| friend class ROOT::Experimental::RFile; | ||||||||||||
| friend class ROOT::Experimental::RFileKeyIterable; | ||||||||||||
|
|
||||||||||||
| public: | ||||||||||||
|
|
@@ -216,6 +223,8 @@ auto myObj = file->Get<TH1D>("h"); | |||||||||||
| ~~~ | ||||||||||||
| */ | ||||||||||||
| class RFile final { | ||||||||||||
| friend void *Internal::RFile_GetObjectFromKey(RFile &file, const RKeyInfo &key); | ||||||||||||
|
|
||||||||||||
| /// Flags used in PutInternal() | ||||||||||||
| enum PutFlags { | ||||||||||||
| /// When encountering an object at the specified path, overwrite it with the new one instead of erroring out. | ||||||||||||
|
|
@@ -231,7 +240,8 @@ class RFile final { | |||||||||||
|
|
||||||||||||
| /// Gets object `path` from the file and returns an **owning** pointer to it. | ||||||||||||
| /// The caller should immediately wrap it into a unique_ptr of the type described by `type`. | ||||||||||||
| [[nodiscard]] void *GetUntyped(std::string_view path, const std::type_info &type) const; | ||||||||||||
| [[nodiscard]] void *GetUntyped(std::string_view path, | ||||||||||||
| std::variant<const char *, std::reference_wrapper<const std::type_info>> type) const; | ||||||||||||
|
Comment on lines
+243
to
+244
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it is private and only use in the source file, would this be an option:
Suggested change
( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can't work because it's also called from the header (in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough. This is then down to a stylistic choice. You could still use the template but with one level of indirection (2 non templated one line overloads implemented in the source file and 1 templated |
||||||||||||
|
|
||||||||||||
| /// Writes `obj` to file, without taking its ownership. | ||||||||||||
| void PutUntyped(std::string_view path, const std::type_info &type, const void *obj, std::uint32_t flags); | ||||||||||||
|
|
@@ -357,6 +367,9 @@ public: | |||||||||||
| return RFileKeyIterable(fFile.get(), basePath, flags); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Retrieves information about the key of object at `path`, if one exists. | ||||||||||||
| std::optional<RKeyInfo> GetKeyInfo(std::string_view path) const; | ||||||||||||
|
|
||||||||||||
| /// Prints the internal structure of this RFile to the given stream. | ||||||||||||
| void Print(std::ostream &out = std::cout) const; | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
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.
It's slightly weird to see these lines here as they do not relate to the
Openmethod per se. Usually these are placed in the function that is wrapped by the@pythonizationdecorator