From ff6d3615e88206ef07a4578b0f1d0950c59c139d Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 3 Nov 2026 11:28:46 +0100 Subject: [PATCH 1/2] [rfile][python] ruff format (cherry picked from commit ea86f9653577f3b0666215c1d9a9b1f2efb29fc4) --- .../python/ROOT/_pythonization/_rfile.py | 9 ++++--- io/io/test/rfile.py | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py index b8f8067c24528..eb05ebb245f34 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py @@ -172,7 +172,7 @@ class _RFile_Overwrite: def __init__(self, rfile): self._rfile = rfile - def __call__(self, path, obj = None, title = "", *, createNewCycle = True): + def __call__(self, path, obj=None, title="", *, createNewCycle=True): """ Non-templated Overwrite() """ @@ -187,6 +187,7 @@ def __call__(self, path, obj = None, title = "", *, createNewCycle = True): className = objType.__cpp_name__ from ROOT import RFile + flags = (0 if createNewCycle else 1) * RFile.kOverwrite_NoNewCycle self._rfile.Overwrite[className](path, obj, title, flags) @@ -273,9 +274,9 @@ def _ListKeys(rfile, basePath="", **kwargs): """ from ROOT import RFile - listObjects = kwargs['listObjects'] if 'listObjects' in kwargs else True - listDirs = kwargs['listDirs'] if 'listDirs' in kwargs else False - listRecursive = kwargs['listRecursive'] if 'listRecursive' in kwargs else True + listObjects = kwargs["listObjects"] if "listObjects" in kwargs else True + listDirs = kwargs["listDirs"] if "listDirs" in kwargs else False + listRecursive = kwargs["listRecursive"] if "listRecursive" in kwargs else True flags = (listObjects * RFile.kListObjects) | (listDirs * RFile.kListDirs) | (listRecursive * RFile.kListRecursive) iter = rfile._OriginalListKeys(basePath, flags) diff --git a/io/io/test/rfile.py b/io/io/test/rfile.py index bd02bfce5b2ab..a3f7f632b57ee 100644 --- a/io/io/test/rfile.py +++ b/io/io/test/rfile.py @@ -8,8 +8,11 @@ def platform_supports_assertRaises(): # TODO: re-enable it on Windows once the exception handling is fixed # TODO: remove mac 15 exception once we stop supporting it - return platform.system() != "Windows" and (platform.system() != "Darwin" or not platform.mac_ver()[0].startswith('15')) - + return platform.system() != "Windows" and ( + platform.system() != "Darwin" or not platform.mac_ver()[0].startswith("15") + ) + + class RFileTests(unittest.TestCase): def test_open_for_reading(self): """A RFile can read a ROOT file created by TFile""" @@ -81,7 +84,6 @@ def test_getkeyinfo(self): finally: os.remove(fileName) - def test_listkeys(self): """A RFile can query the keys of its objects and directories""" @@ -108,17 +110,25 @@ def test_listkeys(self): ["foo/hist", "foo/bar/hist", "foo/bar/hist2", "foo/hist2"], ) - self.assertEqual([key.GetPath() for key in rfile.ListKeys("foo/bar")], ["foo/bar/hist", "foo/bar/hist2"]) + self.assertEqual( + [key.GetPath() for key in rfile.ListKeys("foo/bar")], ["foo/bar/hist", "foo/bar/hist2"] + ) self.assertEqual( [key.GetPath() for key in rfile.ListKeys("", listDirs=True, listObjects=False)], ["foo", "foo/bar"] ) self.assertEqual( - [key.GetPath() for key in rfile.ListKeys("", listDirs=True, listObjects=False, listRecursive=False)], + [ + key.GetPath() + for key in rfile.ListKeys("", listDirs=True, listObjects=False, listRecursive=False) + ], ["foo"], ) self.assertEqual( - [key.GetPath() for key in rfile.ListKeys("foo", listDirs=True, listObjects=False, listRecursive=False)], + [ + key.GetPath() + for key in rfile.ListKeys("foo", listDirs=True, listObjects=False, listRecursive=False) + ], ["foo/bar"], ) self.assertEqual( @@ -236,8 +246,7 @@ def test_overwrite(self): self.assertTrue(isinstance(hist, ROOT.TH1F)) finally: - # os.remove(fileName) - pass + os.remove(fileName) if __name__ == "__main__": From 00b9f42def2aed632357dbfd9c923708c50a95de Mon Sep 17 00:00:00 2001 From: silverweed Date: Tue, 3 Nov 2026 11:21:10 +0100 Subject: [PATCH 2/2] [rfile][python] add support for __getitem__ and __contains__ (cherry picked from commit f646d528c07e4321f360443be333884f24e3daf7) --- .../python/ROOT/_pythonization/_rfile.py | 39 +++++++++++++++++-- io/io/test/rfile.py | 39 +++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py index eb05ebb245f34..e91c6cfabb8f3 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rfile.py @@ -24,6 +24,23 @@ # file.Flush() \endcode +Getting an object can also be done via the dictionary syntax: + +\code{.py} +myHist = file['myHisto'] +\endcode + +The difference between the two methods is that Get will return `None` if the object is missing, while the square +bracket operator will raise a `KeyError`. + +There are two main ways to test for the presence of objects in the file: + +\code{.py} +histoIsThere = 'myHisto' in file +# or +histoIsThere = file.GetKeyInfo('myHisto') is None +\endcode + The Put method will raise an error if an object is already present at the given path; if you want to overwrite an existing object use the Overwrite method instead: @@ -233,14 +250,14 @@ def _RFileInit(rfile): raise NotImplementedError("RFile can only be created via Recreate, Open or Update") -def _GetKeyInfo(rfile, path): +def _RFile_GetKeyInfo(rfile, path): key = rfile._OriginalGetKeyInfo(path) if key.has_value(): return key.value() return None -def _ListKeys(rfile, basePath="", **kwargs): +def _RFile_ListKeys(rfile, basePath="", **kwargs): """ 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). @@ -283,6 +300,18 @@ def _ListKeys(rfile, basePath="", **kwargs): return iter +def _RFile_getitem(rfile, namecycle): + item = rfile.Get(namecycle) + if item is None: + raise KeyError(namecycle) + return item + + +def _RFile_contains(rfile, namecycle): + keyInfo = _RFile_GetKeyInfo(rfile, namecycle) + return keyInfo is not None + + @pythonization("RFile", ns="ROOT") def pythonize_rfile(klass): # Explicitly prevent to create a RFile via ctor @@ -298,6 +327,8 @@ def pythonize_rfile(klass): klass.__enter__ = lambda rfile: rfile klass.__exit__ = _RFileExit klass._OriginalGetKeyInfo = klass.GetKeyInfo - klass.GetKeyInfo = _GetKeyInfo + klass.GetKeyInfo = _RFile_GetKeyInfo klass._OriginalListKeys = klass.ListKeys - klass.ListKeys = _ListKeys + klass.ListKeys = _RFile_ListKeys + klass.__getitem__ = _RFile_getitem + klass.__contains__ = _RFile_contains diff --git a/io/io/test/rfile.py b/io/io/test/rfile.py index a3f7f632b57ee..194b0c06b118f 100644 --- a/io/io/test/rfile.py +++ b/io/io/test/rfile.py @@ -248,6 +248,45 @@ def test_overwrite(self): finally: os.remove(fileName) + def test_getitem(self): + """Objects may be retrieved from RFile via the [] operator. KeyError is thrown if the object isn't there.""" + + fileName = "test_rfile_getitem_py.root" + try: + with ROOT.RFile.Recreate(fileName) as rfile: + hist = ROOT.TH1D("hist", "", 100, -10, 10) + hist.FillRandom("gaus", 10) + rfile.Put("hist", hist) + # There is no setitem support. + if platform_supports_assertRaises(): + with self.assertRaisesRegex(TypeError, "does not support item assignment"): + rfile["hist2"] = hist + + with ROOT.RFile.Open(fileName) as rfile: + hist = rfile["hist"] + self.assertNotEqual(hist, None) + if platform_supports_assertRaises(): + with self.assertRaises(KeyError): + _hist2 = rfile["hist2"] + + finally: + os.remove(fileName) + + def test_contains(self): + """Can query presence of objects via `'key' in rfile`""" + + fileName = "test_rfile_contains_py.root" + try: + with ROOT.RFile.Recreate(fileName) as rfile: + hist = ROOT.TH1D("hist", "", 100, -10, 10) + hist.FillRandom("gaus", 10) + rfile.Put("hist", hist) + self.assertTrue("hist" in rfile) + self.assertFalse("hist2" in rfile) + + finally: + os.remove(fileName) + if __name__ == "__main__": unittest.main()