diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py index a36bcbe51b669..34336b4c87737 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py @@ -127,10 +127,24 @@ class _RFile_Put: def __init__(self, rfile): self._rfile = rfile - def __call__(self, name, obj, title = ""): + def __call__(self, path, obj = None, title = ""): """ Non-templated Put() """ + import ROOT + + if isinstance(path, ROOT.TNamed): + # TNamed-specific overload: arguments are actually Put(namedObj, dirPath). + path, obj = obj, path + fullPath = "" + if isinstance(path, str): + fullPath = f"{path}/" + elif path is not None: + raise TypeError(f"Put(TNamed, dirPath) only accepts a string as the dirPath, not a {type(path)}") + fullPath += obj.GetName() + path = fullPath + title = obj.GetTitle() + objType = type(obj) if isinstance(obj, str): # special case: automatically convert python str to std::string @@ -139,7 +153,7 @@ def __call__(self, name, obj, title = ""): raise TypeError(f"type {objType} is not supported by ROOT I/O") else: className = objType.__cpp_name__ - self._rfile.Put[className](name, obj, title) + self._rfile.Put[className](path, obj, title) def __getitem__(self, template_arg): """ diff --git a/io/io/inc/ROOT/RFile.hxx b/io/io/inc/ROOT/RFile.hxx index 09bbb74fdc450..c3d7fe8637270 100644 --- a/io/io/inc/ROOT/RFile.hxx +++ b/io/io/inc/ROOT/RFile.hxx @@ -258,9 +258,9 @@ class RFile final { /// Flags used in PutInternal() enum EPutFlags { /// When encountering an object at the specified path, overwrite it with the new one instead of erroring out. - kPutFlag_AllowOverwrite = 0x1, + kPutFlag_AllowOverwrite = BIT(0), /// When overwriting an object, preserve the existing one and create a new cycle, rather than removing it. - kPutFlag_OverwriteKeepCycle = 0x2, + kPutFlag_OverwriteKeepCycle = BIT(1), }; std::unique_ptr fFile; @@ -277,9 +277,8 @@ class RFile final { 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, std::string_view title = "") + void PutInternal(std::string_view path, const T &obj, std::uint32_t flags, std::string_view title) { PutUntyped(path, typeid(T), &obj, flags, title); } @@ -361,7 +360,8 @@ public: return std::unique_ptr(static_cast(obj)); } - /// Puts object `obj` into the file, optionally giving it a title. + /// Puts object `obj` into the file and gives it a title. + /// The application retains ownership of the object. /// 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. @@ -378,6 +378,11 @@ public: PutInternal(path, obj, /* flags = */ 0, title); } + /// Puts object `namedObj` in the file under the path `dirPath/namedObj.GetName()`, giving it a title equal to + /// `namedObj.GetTitle()`. + /// If `dirPath` is empty (default), the object is put at the top level of the file. + void Put(const class TNamed &namedObj, std::string_view dirPath = ""); + /// Puts an object into the file, overwriting any previously-existing object at that path. /// See Put for more details. /// diff --git a/io/io/inc/TDirectoryFile.h b/io/io/inc/TDirectoryFile.h index dd0ff02a7f767..af1f578927723 100644 --- a/io/io/inc/TDirectoryFile.h +++ b/io/io/inc/TDirectoryFile.h @@ -125,7 +125,8 @@ 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 char *title, const TClass *cl, const char *name, Option_t *option="", Int_t bufsize=0); + Int_t WriteObjectAny(const void *obj, const TClass *cl, std::pair nameAndTitle, + Option_t *option = "", Int_t bufsize = 0); void WriteDirHeader() override; void WriteKeys() override; diff --git a/io/io/src/RFile.cxx b/io/io/src/RFile.cxx index d7ad9217dbfa7..15e6d786732a3 100644 --- a/io/io/src/RFile.cxx +++ b/io/io/src/RFile.cxx @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -328,6 +329,12 @@ void *RFile::GetUntyped(std::string_view path, return obj; } +void RFile::Put(const TNamed &namedObj, std::string_view dirPath) +{ + std::string completePath = (dirPath.empty() ? "" : std::string(dirPath) + '/') + namedObj.GetName(); + Put(completePath, namedObj, namedObj.GetTitle()); +} + void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, const void *obj, std::uint32_t flags, std::string_view title) { @@ -396,7 +403,7 @@ void RFile::PutUntyped(std::string_view pathSV, const std::type_info &type, cons const char *objName = tokens[tokens.size() - 1].c_str(); assert(dynamic_cast(dir)); int success = - static_cast(dir)->WriteObjectAny(obj, std::string(title).c_str(), cls, objName, writeOpts); + 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")); diff --git a/io/io/src/TDirectoryFile.cxx b/io/io/src/TDirectoryFile.cxx index 9072e9efad3b1..3667e269f785d 100644 --- a/io/io/src/TDirectoryFile.cxx +++ b/io/io/src/TDirectoryFile.cxx @@ -2011,8 +2011,12 @@ Int_t TDirectoryFile::WriteTObject(const TObject *obj, const char *name, Option_ return nbytes; } -Int_t TDirectoryFile::WriteObjectAny(const void *obj, const char *title, const TClass *cl, const char *name, Option_t *option, Int_t bufsize) +Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, + std::pair nameAndTitle, Option_t *option, + Int_t bufsize) { + const auto [name, title] = nameAndTitle; + TDirectory::TContext ctxt(this); if (!fFile) return 0; @@ -2159,7 +2163,7 @@ Int_t TDirectoryFile::WriteObjectAny(const void *obj, const char *classname, con Int_t TDirectoryFile::WriteObjectAny(const void *obj, const TClass *cl, const char *name, Option_t *option, Int_t bufsize) { - return WriteObjectAny(obj, /*title=*/ "", cl, name, option, bufsize); + return WriteObjectAny(obj, cl, {name, ""}, option, bufsize); } //////////////////////////////////////////////////////////////////////////////// diff --git a/io/io/test/rfile.cxx b/io/io/test/rfile.cxx index e6af75e586a6a..410b25b40fa9b 100644 --- a/io/io/test/rfile.cxx +++ b/io/io/test/rfile.cxx @@ -906,3 +906,28 @@ TEST(RFile, Compression) EXPECT_EQ(file->GetCompressionSettings(), 0); } } + +TEST(RFile, PutTNamed) +{ + FileRaii fileGuard("test_rfile_put_tnamed.root"); + + { + TNamed named1("named1", "title 1"), named2("named2", "title 2"); + auto file = RFile::Recreate(fileGuard.GetPath()); + file->Put(named1); + // Put named2 under a dir + file->Put(named2, "my"); + } + + { + auto file = RFile::Open(fileGuard.GetPath()); + auto named1 = file->Get("named1"); + auto named2 = file->Get("my/named2"); + EXPECT_STREQ(named1->GetTitle(), "title 1"); + EXPECT_STREQ(named2->GetTitle(), "title 2"); + auto key1 = file->GetKeyInfo("named1").value(); + auto key2 = file->GetKeyInfo("my/named2").value(); + EXPECT_EQ(key1.GetTitle(), "title 1"); + EXPECT_EQ(key2.GetTitle(), "title 2"); + } +} diff --git a/io/io/test/rfile.py b/io/io/test/rfile.py index 778ab0170841f..bd02bfce5b2ab 100644 --- a/io/io/test/rfile.py +++ b/io/io/test/rfile.py @@ -157,7 +157,7 @@ def test_getTree(self): fileName = "test_rfile_gettree_py.root" try: - with ROOT.TFile.Open(fileName, "RECREATE") as file: + with ROOT.TFile.Open(fileName, "RECREATE") as _file: tree = ROOT.TTree("tree", "tree") x = array("i", [0]) tree.Branch("myColumn", x, "myColumn/I") @@ -175,6 +175,32 @@ def test_getTree(self): finally: os.remove(fileName) + def test_putTNamed(self): + named1 = ROOT.TNamed("named1", "title 1") + named2 = ROOT.TNamed("named2", "title 2") + + fileName = "test_rfile_tnamed_py.root" + try: + with ROOT.RFile.Recreate(fileName) as file: + file.Put(named1) + # Put named2 under a dir + file.Put(named2, "my") + # If first argument is a TNamed, second argument must be a string (or None) + with self.assertRaises(TypeError): + file.Put(named2, [2, 3]) + + with ROOT.RFile.Open(fileName) as file: + named1 = file.Get("named1") + named2 = file.Get("my/named2") + self.assertEqual(named1.GetTitle(), "title 1") + self.assertEqual(named2.GetTitle(), "title 2") + key1 = file.GetKeyInfo("named1") + key2 = file.GetKeyInfo("my/named2") + self.assertEqual(key1.GetTitle(), "title 1") + self.assertEqual(key2.GetTitle(), "title 2") + finally: + os.remove(fileName) + def test_overwrite(self): """Objects can be overwritten through the Overwrite method"""