Skip to content

Commit 7f25518

Browse files
committed
add reading multiple files in get_reader, glob utilities accessible in
python
1 parent 79a4876 commit 7f25518

File tree

7 files changed

+74
-10
lines changed

7 files changed

+74
-10
lines changed

python/podio/reading.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,32 +50,41 @@ def _determine_root_format(filename):
5050
return RootFileFormat.RNTUPLE
5151

5252

53-
def get_reader(filename):
54-
"""Get an appropriate reader for the passed file.
53+
def get_reader(filenames):
54+
"""Get an appropriate reader for the passed files.
55+
The reader is inferred from the first file if multiple are given.
56+
All files are assumed to be of the same I/O format.
5557
5658
Args:
57-
filename (str): The input file
59+
filenames (str or list[str]): The input file(s)
5860
5961
Returns:
6062
root_io.[Legacy]Reader, sio_io.[Legacy]Reader: an initialized reader that
61-
is able to process the input file.
63+
is able to process the input file(s).
6264
6365
Raises:
64-
ValueError: If the file cannot be recognized, or if podio has not been
66+
ValueError: If the files cannot be recognized, or if podio has not been
6567
built with the necessary backend I/O support
68+
IndexError: If filenames is an empty list
6669
"""
70+
71+
if isinstance(filenames, str):
72+
filename = filenames
73+
else:
74+
filename = filenames[0]
75+
6776
if filename.endswith(".sio"):
6877
if _is_frame_sio_file(filename):
69-
return sio_io.Reader(filename)
70-
return sio_io.LegacyReader(filename)
78+
return sio_io.Reader(filenames)
79+
return sio_io.LegacyReader(filenames)
7180

7281
if filename.endswith(".root"):
7382
root_flavor = _determine_root_format(filename)
7483
if root_flavor == RootFileFormat.TTREE:
75-
return root_io.Reader(filename)
84+
return root_io.Reader(filenames)
7685
if root_flavor == RootFileFormat.RNTUPLE:
77-
return root_io.RNTupleReader(filename)
86+
return root_io.RNTupleReader(filenames)
7887
if root_flavor == RootFileFormat.LEGACY:
79-
return root_io.LegacyReader(filename)
88+
return root_io.LegacyReader(filenames)
8089

8190
raise ValueError("file must end on .root or .sio")

python/podio/utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
from collections.abc import Iterable
66
from pathlib import Path
77

8+
from ROOT import gInterpreter
9+
10+
if gInterpreter.LoadFile("podio/podioVersion.h") != 0: # noqa: E402
11+
raise ImportError("Cannot find the podio/podioVersion.h header")
12+
import ROOT.podio.utils # noqa: E402 # pylint: disable=wrong-import-position
13+
814

915
def convert_to_str_paths(filenames):
1016
"""Converts filenames to string paths, handling both string and pathlib.Path objects and
@@ -22,3 +28,30 @@ def convert_to_str_paths(filenames):
2228
return [os.fspath(fn) for fn in filenames]
2329

2430
return [os.fspath(filenames)]
31+
32+
33+
def expand_glob(pattern):
34+
"""
35+
Expands a given glob pattern into a list of matching file paths.
36+
37+
This function takes a glob pattern as input and returns a list of strings
38+
containing the paths that match the pattern. It supports standard glob rules
39+
extended with tilde expansion and brace expansion. If the pattern doesn't
40+
contain any wildcards, it is placed in the returned list as is. Paths that
41+
cannot be accessed are displayed on stderr, but the expansion process is not
42+
aborted.
43+
44+
Args:
45+
pattern (str): The glob pattern to expand.
46+
47+
Returns:
48+
list of str: A list of strings containing the matching file paths.
49+
50+
Raises:
51+
cppyy.gbl.std.runtime_error: If no matches are found or if there is an error during glob
52+
expansion.
53+
"""
54+
return [str(x) for x in ROOT.podio.utils.expand_glob(pattern)]
55+
56+
57+
is_glob_pattern = ROOT.podio.utils.is_glob_pattern

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ SET(core_headers
7070
${PROJECT_SOURCE_DIR}/include/podio/utilities/DatamodelRegistryIOHelpers.h
7171
${PROJECT_SOURCE_DIR}/include/podio/GenericParameters.h
7272
${PROJECT_SOURCE_DIR}/include/podio/LinkCollection.h
73+
${PROJECT_SOURCE_DIR}/include/podio/utilities/Glob.h
7374
)
7475

7576
PODIO_ADD_LIB_AND_DICT(podio "${core_headers}" "${core_sources}" selection.xml)

src/selection.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@
4646
<class name="podio::LinkData"/>
4747
<class name="std::vector<podio::LinkData>"/>
4848

49+
<function name="podio::utilities::is_glob_pattern"/>
50+
<function name="podio::utilities::expand_glob"/>
51+
4952
</selection>
5053
</lcgdict>

tests/CTestCustom.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ if ((NOT "@FORCE_RUN_ALL_TESTS@" STREQUAL "ON") AND (NOT "@USE_SANITIZER@" STREQ
2424
write_frame_root
2525
read_frame_root
2626
read_glob
27+
read_python_multiple
2728

2829
write_interface_root
2930
read_interface_root

tests/root_io/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ set_tests_properties(
4646
DEPENDS write_frame_root
4747
)
4848

49+
add_test(NAME read_python_multiple COMMAND python3 ${PROJECT_SOURCE_DIR}/tests/root_io/read_multiple.py)
50+
PODIO_SET_TEST_ENV(read_python_multiple)
51+
set_property(TEST read_python_multiple PROPERTY DEPENDS write_frame_root)
52+
4953
if(ENABLE_RNTUPLE)
5054
set_property(TEST read_rntuple PROPERTY DEPENDS write_rntuple)
5155
set_property(TEST read_interface_rntuple PROPERTY DEPENDS write_interface_rntuple)

tests/root_io/read_multiple.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Small test case for checking get_reader working with
2+
a single file, list of files, and a glob pattern"""
3+
4+
import podio
5+
6+
assert podio.utils.is_glob_pattern("example_frame_?.root")
7+
files = podio.utils.expand_glob("example_frame_?.root")
8+
assert files == ["example_frame_0.root", "example_frame_1.root"]
9+
10+
reader = podio.reading.get_reader("example_frame.root")
11+
assert len(reader.get("events")) == 10
12+
reader = podio.reading.get_reader(files)
13+
assert len(reader.get("events")) == 20

0 commit comments

Comments
 (0)