From 6137da59a17016631553a9baf38b89fcd39e0fc6 Mon Sep 17 00:00:00 2001 From: silverweed Date: Mon, 20 Jul 2026 14:06:26 +0200 Subject: [PATCH 1/4] [rfile] Some minor fixups to RFile (mostly doc comments) --- io/io/inc/ROOT/RFile.hxx | 51 +++++++++++++++++++++++----------------- io/io/src/RFile.cxx | 4 ++-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/io/io/inc/ROOT/RFile.hxx b/io/io/inc/ROOT/RFile.hxx index 40bd0a2ff31fd..1d2da6185a8b7 100644 --- a/io/io/inc/ROOT/RFile.hxx +++ b/io/io/inc/ROOT/RFile.hxx @@ -254,11 +254,11 @@ class RFile final { friend TFile *Internal::GetRFileTFile(RFile &rfile); /// Flags used in PutInternal() - enum PutFlags { + enum EPutFlags { /// When encountering an object at the specified path, overwrite it with the new one instead of erroring out. - kPutAllowOverwrite = 0x1, + kPutFlag_AllowOverwrite = 0x1, /// When overwriting an object, preserve the existing one and create a new cycle, rather than removing it. - kPutOverwriteKeepCycle = 0x2, + kPutFlag_OverwriteKeepCycle = 0x2, }; std::unique_ptr fFile; @@ -328,21 +328,24 @@ public: /// Retrieves an object from the file. /// `path` should be a string such that `IsValidPath(path) == true`, otherwise an exception will be thrown. /// See \ref ValidateAndNormalizePath() for info about valid path names. - /// If the object is not there returns a null pointer. + /// \return A copy of the object at `path`, or `nullptr` if there is no object of type `T` at the specified path. template std::unique_ptr Get(std::string_view path) const { void *obj = GetUntyped(path, typeid(T)); return std::unique_ptr(static_cast(obj)); } - - /// Puts an object into the file. - /// The application retains ownership of the object. + + /// Puts object `obj` into the file. + /// The object will be effectively copied into the file, so any further modifications won't be seen by the object + /// inside the file. + /// Note that the object is not necessarily written to storage until the RFile is closed or `Flush()` is called. + /// /// `path` should be a string such that `IsValidPath(path) == true`, otherwise an exception will be thrown. /// See \ref ValidateAndNormalizePath() for info about valid path names. /// - /// Throws a RException if `path` already identifies a valid object or directory. - /// Throws a RException if the file was opened in read-only mode. + /// \throws ROOT::RException if `path` already identifies a valid object or directory. + /// \throws ROOT::RException if the file was opened in read-only mode. template void Put(std::string_view path, const T &obj) { @@ -350,23 +353,25 @@ public: } /// Puts an object into the file, overwriting any previously-existing object at that path. - /// The application retains ownership of the object. + /// See Put for more details. /// - /// If an object already exists at that path, it is kept as a backup cycle unless `backupPrevious` is false. - /// Note that even if `backupPrevious` is false, any existing cycle except the latest will be preserved. + /// If an object already exists at that path,AND it has type `T`, it is kept as a backup cycle + /// unless `createNewCycle` is false. + /// If the existing object's type differs from `T`, an exception is thrown. + /// Note that even if `createNewCycle` is false only the *latest* cycle will be deleted. /// - /// Throws a RException if `path` is already the path of a directory. - /// Throws a RException if the file was opened in read-only mode. + /// \throws ROOT::RException if `path` is already the path of a directory. + /// \throws ROOT::RException if the file was opened in read-only mode. template - void Overwrite(std::string_view path, const T &obj, bool backupPrevious = true) + void Overwrite(std::string_view path, const T &obj, bool createNewCycle = true) { - std::uint32_t flags = kPutAllowOverwrite; - flags |= backupPrevious * kPutOverwriteKeepCycle; + std::uint32_t flags = kPutFlag_AllowOverwrite; + flags |= createNewCycle * kPutFlag_OverwriteKeepCycle; PutInternal(path, obj, flags); } /// Writes all objects and the file structure to disk. - /// Returns the number of bytes written. + /// \return the number of bytes written. size_t Flush(); /// Flushes the RFile if needed and closes it, disallowing any further reading or writing. @@ -374,6 +379,8 @@ public: /// Returns an iterable over all keys of objects and/or directories written into this RFile starting at path /// `basePath` (defaulting to include the content of all subdirectories). + /// The iterable yields values of type ROOT::Experimental::RKeyInfo. + /// /// By default, keys referring to directories are not returned: only those referring to leaf objects are. /// If `basePath` is the path of a leaf object, only `basePath` itself will be returned. /// If `basePath` is the path of a directory, it won't appear in the listing. @@ -386,17 +393,17 @@ public: /// Example usage: /// ~~~{.cpp} /// for (RKeyInfo key : file->ListKeys()) { - /// /* iterate over all objects in the RFile */ + /// // iterate over all objects in the RFile /// cout << key.GetPath() << ";" << key.GetCycle() << " of type " << key.GetClassName() << "\n"; /// } /// for (RKeyInfo key : file->ListKeys("", kListDirs|kListObjects|kListRecursive)) { - /// /* iterate over all objects and directories in the RFile */ + /// // iterate over all objects and directories in the RFile /// } /// for (RKeyInfo key : file->ListKeys("a/b", kListObjects)) { - /// /* iterate over all objects that are immediate children of directory "a/b" */ + /// // iterate over all objects that are immediate children of directory "a/b" /// } /// for (RKeyInfo key : file->ListKeys("foo", kListDirs|kListRecursive)) { - /// /* iterate over all directories under directory "foo", recursively */ + /// // iterate over all directories under directory "foo", recursively /// } /// ~~~ RFileKeyIterable ListKeys(std::string_view basePath = "", std::uint32_t flags = kListObjects | kListRecursive) const diff --git a/io/io/src/RFile.cxx b/io/io/src/RFile.cxx index bc80ffa04170c..fef24d48c6cc6 100644 --- a/io/io/src/RFile.cxx +++ b/io/io/src/RFile.cxx @@ -377,8 +377,8 @@ void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, cons } } - const bool allowOverwrite = (flags & kPutAllowOverwrite) != 0; - const bool backupCycle = (flags & kPutOverwriteKeepCycle) != 0; + const bool allowOverwrite = (flags & kPutFlag_AllowOverwrite) != 0; + const bool backupCycle = (flags & kPutFlag_OverwriteKeepCycle) != 0; const Option_t *writeOpts = ""; if (!allowOverwrite) { const TKey *existing = dir->GetKey(tokens[tokens.size() - 1].c_str()); From 7b0ddb8f6e49a56130450dd11c672ce6616049c8 Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 21 Jul 2026 16:36:17 +0200 Subject: [PATCH 2/4] [io] Add overload of TDirectoryFile::WriteObjectAny accepting title Then call it from the current WriteObjectAny --- io/io/inc/TDirectoryFile.h | 1 + io/io/src/TDirectoryFile.cxx | 120 ++++++++++++++++++----------------- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/io/io/inc/TDirectoryFile.h b/io/io/inc/TDirectoryFile.h index 16f2d2edaa4e4..249d0ef155268 100644 --- a/io/io/inc/TDirectoryFile.h +++ b/io/io/inc/TDirectoryFile.h @@ -125,6 +125,7 @@ class TDirectoryFile : public TDirectory { Int_t WriteTObject(const TObject *obj, const char *name=nullptr, Option_t *option="", Int_t bufsize=0) override; Int_t WriteObjectAny(const void *obj, const char *classname, const char *name, Option_t *option="", Int_t bufsize=0) override; Int_t WriteObjectAny(const void *obj, const TClass *cl, const char *name, Option_t *option="", Int_t bufsize=0) override; + Int_t WriteObjectAny(const void *obj, const TClass *cl, const char *name, const char *title, Option_t *option="", Int_t bufsize=0); void WriteDirHeader() override; void WriteKeys() override; diff --git a/io/io/src/TDirectoryFile.cxx b/io/io/src/TDirectoryFile.cxx index 2f598d32296a7..115a6b137fb36 100644 --- a/io/io/src/TDirectoryFile.cxx +++ b/io/io/src/TDirectoryFile.cxx @@ -2010,63 +2010,7 @@ Int_t TDirectoryFile::WriteTObject(const TObject *obj, const char *name, Option_ return nbytes; } -//////////////////////////////////////////////////////////////////////////////// -/// Write object from pointer of class classname in this directory. -/// -/// obj may not derive from TObject. See TDirectoryFile::WriteTObject for comments -/// -/// ## Very important note -/// The value passed as 'obj' needs to be from a pointer to the type described by classname. -/// For example: -/// ~~~{.cpp} -/// TopClass *top; -/// BottomClass *bottom; -/// top = bottom; -/// ~~~ -/// you can do: -/// ~~~{.cpp} -/// directory->WriteObjectAny(top,"top","name of object"); -/// directory->WriteObjectAny(bottom,"bottom","name of object"); -/// ~~~ -/// BUT YOU CAN NOT DO the following since it will fail with multiple inheritance: -/// ~~~{.cpp} -/// directory->WriteObjectAny(top,"bottom","name of object"); -/// ~~~ -/// We STRONGLY recommend to use -/// ~~~{.cpp} -/// TopClass *top = ....; -/// directory->WriteObject(top,"name of object") -/// ~~~ -/// See also remarks in TDirectoryFile::WriteTObject - -Int_t TDirectoryFile::WriteObjectAny(const void *obj, const char *classname, const char *name, Option_t *option, Int_t bufsize) -{ - TClass *cl = TClass::GetClass(classname); - if (!cl) { - TObject *info_obj = *(TObject**)obj; - TVirtualStreamerInfo *info = dynamic_cast(info_obj); - if (!info) { - Error("WriteObjectAny","Unknown class: %s",classname); - return 0; - } else { - cl = info->GetClass(); - } - } - return WriteObjectAny(obj,cl,name,option,bufsize); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Write object of class with dictionary cl in this directory. -/// -/// obj may not derive from TObject -/// To get the TClass* cl pointer, one can use -/// -/// TClass *cl = TClass::GetClass("classname"); -/// -/// An alternative is to call the function WriteObjectAny above. -/// see TDirectoryFile::WriteTObject for comments - -Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const char *name, Option_t *option, Int_t bufsize) +Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const char *name, const char *title, Option_t *option, Int_t bufsize) { TDirectory::TContext ctxt(this); @@ -2136,6 +2080,7 @@ Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const ch oldkey = GetKey(oname); } key = fFile->CreateKey(this, obj, cl, oname, bsize); + key->SetTitle(title); if (newName) delete [] newName; if (!key->GetSeekKey()) { @@ -2155,6 +2100,67 @@ Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const ch return nbytes; } +//////////////////////////////////////////////////////////////////////////////// +/// Write object from pointer of class classname in this directory. +/// +/// obj may not derive from TObject. See TDirectoryFile::WriteTObject for comments +/// +/// ## Very important note +/// The value passed as 'obj' needs to be from a pointer to the type described by classname. +/// For example: +/// ~~~{.cpp} +/// TopClass *top; +/// BottomClass *bottom; +/// top = bottom; +/// ~~~ +/// you can do: +/// ~~~{.cpp} +/// directory->WriteObjectAny(top,"top","name of object"); +/// directory->WriteObjectAny(bottom,"bottom","name of object"); +/// ~~~ +/// BUT YOU CAN NOT DO the following since it will fail with multiple inheritance: +/// ~~~{.cpp} +/// directory->WriteObjectAny(top,"bottom","name of object"); +/// ~~~ +/// We STRONGLY recommend to use +/// ~~~{.cpp} +/// TopClass *top = ....; +/// directory->WriteObject(top,"name of object") +/// ~~~ +/// See also remarks in TDirectoryFile::WriteTObject + +Int_t TDirectoryFile::WriteObjectAny(const void *obj, const char *classname, const char *name, Option_t *option, Int_t bufsize) +{ + TClass *cl = TClass::GetClass(classname); + if (!cl) { + TObject *info_obj = *(TObject**)obj; + TVirtualStreamerInfo *info = dynamic_cast(info_obj); + if (!info) { + Error("WriteObjectAny","Unknown class: %s",classname); + return 0; + } else { + cl = info->GetClass(); + } + } + return WriteObjectAny(obj,cl,name,option,bufsize); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Write object of class with dictionary cl in this directory. +/// +/// obj may not derive from TObject +/// To get the TClass* cl pointer, one can use +/// +/// TClass *cl = TClass::GetClass("classname"); +/// +/// An alternative is to call the function WriteObjectAny above. +/// see TDirectoryFile::WriteTObject for comments + +Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const char *name, Option_t *option, Int_t bufsize) +{ + return WriteObjectAny(obj, cl, name, /*title=*/ "", option, bufsize); +} + //////////////////////////////////////////////////////////////////////////////// /// Overwrite the Directory header record. From cbffb23a3492268e5fb1a07d4c557263b6a6af60 Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 21 Jul 2026 16:37:36 +0200 Subject: [PATCH 3/4] [rfile] Add possibility to set object title in RFile::PutInternal --- io/io/inc/ROOT/RFile.hxx | 7 ++++--- io/io/src/RFile.cxx | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/io/io/inc/ROOT/RFile.hxx b/io/io/inc/ROOT/RFile.hxx index 1d2da6185a8b7..381218982b0c1 100644 --- a/io/io/inc/ROOT/RFile.hxx +++ b/io/io/inc/ROOT/RFile.hxx @@ -272,13 +272,14 @@ class RFile final { std::variant> type) const; /// 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); + void PutUntyped(std::string_view path, const std::type_info &type, const void *obj, std::uint32_t flags, + std::string_view title); /// \see Put template - void PutInternal(std::string_view path, const T &obj, std::uint32_t flags) + void PutInternal(std::string_view path, const T &obj, std::uint32_t flags, std::string_view title = "") { - PutUntyped(path, typeid(T), &obj, flags); + PutUntyped(path, typeid(T), &obj, flags, title); } /// Given `path`, returns the TKey corresponding to the object at that path (assuming the path is fully split, i.e. diff --git a/io/io/src/RFile.cxx b/io/io/src/RFile.cxx index fef24d48c6cc6..626828b122aea 100644 --- a/io/io/src/RFile.cxx +++ b/io/io/src/RFile.cxx @@ -326,7 +326,8 @@ void *RFile::GetUntyped(std::string_view path, return obj; } -void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, const void *obj, std::uint32_t flags) +void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, const void *obj, std::uint32_t flags, + std::string_view title) { const TClass *cls = TClass::GetClass(type); if (!cls) @@ -390,7 +391,10 @@ void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, cons writeOpts = "WriteDelete"; } - int success = dir->WriteObjectAny(obj, cls, tokens[tokens.size() - 1].c_str(), writeOpts); + const char *objName = tokens[tokens.size() - 1].c_str(); + assert(dynamic_cast(dir)); + int success = + static_cast(dir)->WriteObjectAny(obj, cls, objName, std::string(title).c_str(), writeOpts); if (!success) { throw ROOT::RException(R__FAIL(std::string("Failed to write ") + path + " to file")); From 4741af96bddfcb2794054171375b1114d93e815e Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 21 Jul 2026 17:06:28 +0200 Subject: [PATCH 4/4] [rfile] Add possibility of setting an object title when writing --- .../python/ROOT/_pythonization/_rfile.py | 4 ++-- io/io/inc/ROOT/RFile.hxx | 17 ++++++++++++----- io/io/test/rfile.cxx | 6 +++++- io/io/test/rfile.py | 3 ++- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py index 727835e1618ea..b015f51e27a84 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py @@ -58,7 +58,7 @@ class _RFile_Put: def __init__(self, rfile): self._rfile = rfile - def __call__(self, name, obj): + def __call__(self, name, obj, title = ""): """ Non-templated Put() """ @@ -70,7 +70,7 @@ def __call__(self, name, obj): raise TypeError(f"type {objType} is not supported by ROOT I/O") else: className = objType.__cpp_name__ - self._rfile.Put[className](name, obj) + self._rfile.Put[className](name, obj, title) def __getitem__(self, template_arg): """ diff --git a/io/io/inc/ROOT/RFile.hxx b/io/io/inc/ROOT/RFile.hxx index 381218982b0c1..33b6f72aa93ee 100644 --- a/io/io/inc/ROOT/RFile.hxx +++ b/io/io/inc/ROOT/RFile.hxx @@ -336,8 +336,8 @@ public: void *obj = GetUntyped(path, typeid(T)); return std::unique_ptr(static_cast(obj)); } - - /// Puts object `obj` into the file. + + /// Puts object `obj` into the file, optionally giving it a title. /// The object will be effectively copied into the file, so any further modifications won't be seen by the object /// inside the file. /// Note that the object is not necessarily written to storage until the RFile is closed or `Flush()` is called. @@ -348,9 +348,9 @@ public: /// \throws ROOT::RException if `path` already identifies a valid object or directory. /// \throws ROOT::RException if the file was opened in read-only mode. template - void Put(std::string_view path, const T &obj) + void Put(std::string_view path, const T &obj, std::string_view title = "") { - PutInternal(path, obj, /* flags = */ 0); + PutInternal(path, obj, /* flags = */ 0, title); } /// Puts an object into the file, overwriting any previously-existing object at that path. @@ -365,10 +365,17 @@ public: /// \throws ROOT::RException if the file was opened in read-only mode. template void Overwrite(std::string_view path, const T &obj, bool createNewCycle = true) + { + Overwrite(path, obj, "", createNewCycle); + } + + /// Like Overwrite(std::string_view, const T&, bool) but allows giving a title to the object. + template + void Overwrite(std::string_view path, const T &obj, std::string_view title, bool createNewCycle = true) { std::uint32_t flags = kPutFlag_AllowOverwrite; flags |= createNewCycle * kPutFlag_OverwriteKeepCycle; - PutInternal(path, obj, flags); + PutInternal(path, obj, flags, title); } /// Writes all objects and the file structure to disk. diff --git a/io/io/test/rfile.cxx b/io/io/test/rfile.cxx index 14e45e5be2fd9..bd308353d1b03 100644 --- a/io/io/test/rfile.cxx +++ b/io/io/test/rfile.cxx @@ -231,13 +231,15 @@ TEST(RFile, PutOverwrite) { TH1D hist("hist", "", 100, -10, 10); hist.FillRandom("gaus", 1000); - file->Put("hist", hist); + file->Put("hist", hist, "My Histo"); } { auto hist = file->Get("hist"); ASSERT_TRUE(hist); EXPECT_EQ(static_cast(hist->GetEntries()), 1000); + auto key = file->GetKeyInfo("hist").value(); + EXPECT_EQ(key.GetTitle(), "My Histo"); } // Try putting another object at the same path, should fail @@ -267,6 +269,8 @@ TEST(RFile, PutOverwrite) // ...but any cycle before the latest should still be there! hist = file->Get("hist;1"); EXPECT_NE(hist, nullptr); + auto key = file->GetKeyInfo("hist;1").value(); + EXPECT_EQ(key.GetTitle(), "My Histo"); } } diff --git a/io/io/test/rfile.py b/io/io/test/rfile.py index 30d78872665b1..3853c05d4f23f 100644 --- a/io/io/test/rfile.py +++ b/io/io/test/rfile.py @@ -63,7 +63,7 @@ def test_getkeyinfo(self): with RFile.Recreate(fileName) as rfile: hist = ROOT.TH1D("hist", "", 100, -10, 10) hist.FillRandom("gaus", 10) - rfile.Put("hist", hist) + rfile.Put("hist", hist, "My Histo") rfile.Put("foo/hist", hist) rfile.Put("foo/bar/hist", hist) rfile.Put("foo/bar/hist2", hist) @@ -73,6 +73,7 @@ def test_getkeyinfo(self): key = rfile.GetKeyInfo("hist") self.assertEqual(key.GetPath(), "hist") self.assertEqual(key.GetClassName(), "TH1D") + self.assertEqual(key.GetTitle(), "My Histo") key = rfile.GetKeyInfo("does_not_exist") self.assertEqual(key, None)