Skip to content

Commit

Permalink
Fix overwritting the input file for IOSvc from the command line
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcarcell committed Oct 1, 2024
1 parent ac29f06 commit 953cca7
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 79 deletions.
6 changes: 5 additions & 1 deletion k4FWCore/scripts/k4run
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ FILTER_GAUDI_PROPS = [
"NeededResources",
"PropertiesPrint",
"RegisterForContextService",
"RegularRowFormat",
"RequireObjects",
"RootInTES",
"StatPrint",
Expand Down Expand Up @@ -224,6 +223,11 @@ def main():
# Allow graceful exit with Ctrl + C
ApplicationMgr().StopOnSignal = True

# Apply fixes from the k4FWCore wrapper

from k4FWCore import ApplicationMgr
ApplicationMgr().fix_properties()

from Gaudi.Main import gaudimain

gaudi = gaudimain()
Expand Down
164 changes: 86 additions & 78 deletions python/k4FWCore/ApplicationMgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from Configurables import ApplicationMgr as AppMgr
from Configurables import Reader, Writer, IOSvc, MetadataSvc, Gaudi__Sequencer, EventLoopMgr
import os
from Configurables import ApplicationMgr as AppMgr
from Configurables import Reader, Writer, IOSvc, Gaudi__Sequencer, EventLoopMgr
from podio.root_io import Reader as PodioReader


Expand All @@ -37,89 +37,97 @@ class ApplicationMgr:

def __init__(self, **kwargs):
self._mgr = AppMgr(**kwargs)

def fix_properties(self):
# If there isn't an EventLoopMgr then it's the default
# This will suppress two warnings about not using external input
try:
self._mgr.EventLoop
except AttributeError:
self._mgr.EventLoop = EventLoopMgr(Warnings=False)

for conf in frozenset(self._mgr.allConfigurables.values()):
if isinstance(conf, MetadataSvc):
self._mgr.ExtSvc.append(conf)
if not isinstance(conf, IOSvc):
continue
props = conf.getPropertiesWithDescription()
reader = writer = None
add_reader = False
for alg in self._mgr.TopAlg:
if isinstance(alg, Reader):
reader = alg
elif isinstance(alg, Writer):
writer = alg
# Remove "input" when the corresponding property is removed from IOSvc
if reader is None and (props["input"][0] or props["Input"][0]):
reader = Reader("k4FWCore__Reader")
add_reader = True
# It seems for a single string the default without a value is '<no value>'
# while for a list it's an empty list
# Remove "output" when the corresponding property is removed from IOSvc
if (
writer is None
and (props["output"][0] and props["output"][0] != "<no value>")
or (props["Output"][0] and props["Output"][0] != "<no value>")
):
writer = Writer("k4FWCore__Writer")
# Let's tell the Reader one of the input files so it can
# know which collections it's going to read
if reader is not None:
# Open the files and get the number of events. This is necessary to
# avoid errors when running multithreaded since if we have, for
# example, 10 events and we are running 9 at the same time, then
# (possibly) the first 9 complete and 9 more are scheduled, out of
# which only one will be finished without errors. If we know the
# number of events in advance then we can just schedule those.
inp = None
if props["input"][0]:
inp = "input"
elif props["Input"][0]:
inp = "Input"
if inp:
if os.path.exists(props[inp][0][0]):
path = props[inp][0][0]
else:
path = os.getcwd() + "/" + props[inp][0][0]
podio_reader = PodioReader(path)
if self._mgr.EvtMax == -1:
self._mgr.EvtMax = podio_reader._reader.getEntries("events")
try:
frame = podio_reader.get("events")[0]
except IndexError:
print("Warning, the events category wasn't found in the input file")
raise
collections = list(frame.getAvailableCollections())
reader.InputCollections = collections
self._mgr.TopAlg = ([reader] if add_reader else []) + self._mgr.TopAlg
# Assume the writer is at the end
# Algorithms are wrapped with Sequential=False so that they can run in parallel
# The algorithms and Writer are wrapped with Sequential=True so that they can not
# run in parallel
if writer:
self._mgr.TopAlg = [
Gaudi__Sequencer(
"k4FWCore__Sequencer",
Sequential=True,
Members=[
Gaudi__Sequencer(
"k4FWCore__Algs",
Members=self._mgr.TopAlg,
Sequential=False,
ShortCircuit=False,
),
writer,
],
)
]
if 'MetadataSvc' in self._mgr.allConfigurables:
self._mgr.ExtSvc.append(self._mgr.allConfigurables['MetadataSvc'])

if 'IOSvc' not in self._mgr.allConfigurables:
return
if not isinstance(self._mgr.allConfigurables['IOSvc'], IOSvc):
raise TypeError("The IOSvc is not an instance of IOSvc")
conf = self._mgr.allConfigurables['IOSvc']

props = conf.getPropertiesWithDescription()
reader = writer = None
add_reader = False
for alg in self._mgr.TopAlg:
if isinstance(alg, Reader):
reader = alg
elif isinstance(alg, Writer):
writer = alg
# Remove "input" when the corresponding property is removed from IOSvc
if reader is None and (props["input"][0] or props["Input"][0]):
reader = Reader("k4FWCore__Reader")
add_reader = True
# It seems for a single string the default without a value is '<no value>'
# while for a list it's an empty list
# Remove "output" when the corresponding property is removed from IOSvc
if (
writer is None
and (props["output"][0] and props["output"][0] != "<no value>")
or (props["Output"][0] and props["Output"][0] != "<no value>")
):
writer = Writer("k4FWCore__Writer")
# Let's tell the Reader one of the input files so it can
# know which collections it's going to read
if reader is not None:
# Open the files and get the number of events. This is necessary to
# avoid errors when running multithreaded since if we have, for
# example, 10 events and we are running 9 at the same time, then
# (possibly) the first 9 complete and 9 more are scheduled, out of
# which only one will be finished without errors. If we know the
# number of events in advance then we can just schedule those.
inp = None
if props["input"][0]:
inp = "input"
elif props["Input"][0]:
inp = "Input"
if inp:
print(f"Reading collections from {props[inp][0][0]}")
# breakpoint()
if os.path.exists(props[inp][0][0]):
path = props[inp][0][0]
else:
path = os.getcwd() + "/" + props[inp][0][0]
podio_reader = PodioReader(path)
if self._mgr.EvtMax == -1:
self._mgr.EvtMax = podio_reader._reader.getEntries("events")
try:
frame = podio_reader.get("events")[0]
except IndexError:
print("Warning, the events category wasn't found in the input file")
raise
collections = list(frame.getAvailableCollections())
reader.InputCollections = collections
self._mgr.TopAlg = ([reader] if add_reader else []) + self._mgr.TopAlg
# Assume the writer is at the end
# Algorithms are wrapped with Sequential=False so that they can run in parallel
# The algorithms and Writer are wrapped with Sequential=True so that they can not
# run in parallel
if writer:
self._mgr.TopAlg = [
Gaudi__Sequencer(
"k4FWCore__Sequencer",
Sequential=True,
Members=[
Gaudi__Sequencer(
"k4FWCore__Algs",
Members=self._mgr.TopAlg,
Sequential=False,
ShortCircuit=False,
),
writer,
],
)
]

def __getattr__(self, name):
return getattr(self._mgr, name)
Expand Down
3 changes: 3 additions & 0 deletions test/k4FWCoreTest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ add_test_with_env(FunctionalMetadataRead options/ExampleFunctionalMetadataRead.p
add_test_with_env(FunctionalMetadataOldAlgorithm options/ExampleFunctionalMetadataOldAlgorithm.py ADD_TO_CHECK_FILES)
add_test_with_env(createEventHeaderConcurrent options/createEventHeaderConcurrent.py ADD_TO_CHECK_FILES)
add_test_with_env(FunctionalMetadataReadOldAlgorithm options/ExampleFunctionalMetadataReadOldAlgorithm.py PROPERTIES DEPENDS ExampleFunctionalMetadataOldAlgorithm ADD_TO_CHECK_FILES)
add_test_with_env(FunctionalNonExistingFile options/ExampleFunctionalNonExistingFile.py)
set_tests_properties(FunctionalNonExistingFile PROPERTIES PASS_REGULAR_EXPRESSION "File.*couldn't be found")
add_test_with_env(FunctionalNonExistingFileOverwritten options/ExampleFunctionalNonExistingFile.py --IOSvc.Input functional_producer.root)

add_test_with_env(FunctionalWrongImport options/ExampleFunctionalWrongImport.py)
set_tests_properties(FunctionalWrongImport PROPERTIES PASS_REGULAR_EXPRESSION "ImportError: Importing ApplicationMgr or IOSvc from Configurables is not allowed.")
Expand Down
40 changes: 40 additions & 0 deletions test/k4FWCoreTest/options/ExampleFunctionalNonExistingFile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Copyright (c) 2014-2024 Key4hep-Project.
#
# This file is part of Key4hep.
# See https://key4hep.github.io/key4hep-doc/ for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# This is an example reading from a file that doesn't exist, it will fail

from Gaudi.Configuration import INFO
from Configurables import ExampleFunctionalTransformer
from Configurables import EventDataSvc
from k4FWCore import ApplicationMgr, IOSvc

svc = IOSvc()
svc.Input = "non_existing_file.root"

transformer = ExampleFunctionalTransformer(
"Transformer", InputCollection=["MCParticles"], OutputCollection=["NewMCParticles"]
)

mgr = ApplicationMgr(
TopAlg=[transformer],
EvtSel="NONE",
EvtMax=-1,
ExtSvc=[EventDataSvc("EventDataSvc")],
OutputLevel=INFO,
)

0 comments on commit 953cca7

Please sign in to comment.