ATLAS Offline Software
Loading...
Searching...
No Matches
SaveConfigUtils.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3from AnaAlgorithm.AlgSequence import AlgSequence
4from AnaAlgorithm.PythonConfig import PrivateToolConfig
5from AnaAlgorithm.Logging import logging
6
7import os
8import json
9import re
10
11# A tool declaration value has the shape 'Type/Name', with a (possibly namespace-qualified
12# or templated) C++ type name and a tool name made of identifier components separated by dots
13_toolDeclarationExpr = re.compile(r'[A-Za-z_][A-Za-z0-9_:<>,]*/[A-Za-z_][A-Za-z0-9_.]*')
14
15def combine_tools_and_algorithms_ELjob(combine_dictionaries = True, text_file = 'tool_config.txt',
16 alg_file = 'alg_sequence.json', output_file = 'my_analysis_config.json'):
17 """
18 Parse a tool configuration text file and merge it with an algorithm sequence JSON file.
19
20 Args:
21 combine_dictionaries (bool): Whether to load and merge with existing alg_file
22 text_file (str): Path to the tool configuration text file
23 alg_file (str): Path to the algorithm sequence JSON file
24 output_file (str): Path where the merged configuration will be saved
25
26 Returns:
27 dict: The resulting configuration, which is a merge of the tool configuration
28 and algorithm sequence JSON data.
29 """
30 log = logging.getLogger("SaveConfigUtils")
31 log.info("Calling COMBINE_JSON_FILES with the following arguments:")
32 log.info(f" - tool configuration = {text_file}")
33 log.info(f" - algorithm sequence = {alg_file}")
34 log.info(f" - full output config = {output_file}")
35
36 if not os.path.exists(text_file):
37 raise FileNotFoundError(f"The tool configuration file {text_file} was not found.")
38 if not os.path.exists(alg_file):
39 raise FileNotFoundError(f"The algorithm sequence file {alg_file} was not found.")
40
41 # Load or initialize the configuration
42 config = {}
43 if combine_dictionaries:
44 with open(alg_file, 'r', encoding='utf-8') as f:
45 try:
46 config = json.load(f)
47 except json.JSONDecodeError as e:
48 raise ValueError(f"Error parsing JSON in {alg_file}: {e}") from e
49
50 # Process the tool configuration file
51 current_tool_path = None
52 current_tool_id = None
53 current_tool_properties = {}
54
55 with open(text_file, 'r') as f:
56 for line in f:
57 line = line.strip()
58 if not line or '=' not in line:
59 continue
60
61 path, value = map(str.strip, line.split('=', 1))
62 parts = path.split('.')
63
64 # There are two possibilities for 'parts':
65 # 1. Declaration of a new tool
66 # 2. Setting a property on an existing tool
67 if _toolDeclarationExpr.fullmatch(value) and current_tool_path != parts:
68 # We are in case 1, meaning we are done with the previous tool (if any)
69 # Start by saving the settings of the previous tool
70 _register_settings(config, current_tool_path, current_tool_id, current_tool_properties)
71 # Then register the new tool
72 tool_path = parts[:-1]
73 tool_id = parts[-1]
74 tool_name = value.split("/")[1]
75 _register_new_tool(config, tool_path, tool_id, tool_name)
76 # We are ready to collect information about the new tool
77 current_tool_path = tool_path
78 current_tool_id = tool_id
79 current_tool_properties = {}
80 else:
81 # We are in case 2: record new settings
82 property_name = parts[-1]
83 current_tool_properties[property_name] = value
84
85 # Register any remaining settings for the last tool if necessary
86 if current_tool_properties and current_tool_id:
87 _register_settings(config, current_tool_path, current_tool_id, current_tool_properties)
88
89 # Write output
90 with open(output_file, 'w', encoding='utf-8') as f:
91 json.dump(config, f, indent=4)
92
93 return config
94
95
96def _register_settings(config, path, name, settings):
97 if not settings or path is None: return
98 current_dict = config
99
100 # Iterate through the path to reach the correct nested dictionary
101 for key in path:
102 if key not in current_dict:
103 raise ValueError(f"Couldn't find key {key} in dictionary of algorithms and tools, this should not happen!")
104 current_dict = current_dict[key]["Properties"]["Tools"]
105
106 for key,value in settings.items():
107 _safe_add_property(key, value, current_dict[name])
108
109
110def _register_new_tool(config, path, tool_id, name):
111 current_dict = config
112
113 # Iterate through the path to reach the correct nested dictionary
114 for key in path:
115 if key not in current_dict:
116 # Create the entry with the appropriate structure
117 current_dict[key] = {"Properties": {"Tools": {}}, "Type": ""}
118 else:
119 # Enforce the appropriate structure
120 if "Properties" not in current_dict[key].keys():
121 current_dict[key]["Properties"] = {"Tools": {}}
122 if "Tools" not in current_dict[key]["Properties"].keys():
123 current_dict[key]["Properties"]["Tools"] = {}
124 current_dict = current_dict[key]["Properties"]["Tools"]
125
126 # Ensure the tool id exists as a key with a dictionary as its value
127 if tool_id not in current_dict:
128 current_dict[tool_id] = {"Properties": {}}
129
130 # Set the 'Name:' key with the given name
131 if "Properties" not in current_dict[tool_id]:
132 current_dict[tool_id]["Properties"] = {}
133 current_dict[tool_id]["Properties"]["Name"] = name
134
135
136def save_algs_from_sequence_ELjob(sequence, output_dict):
137 """
138 Save the algorithms in the sequence without the sequence structure.
139 Does not save the tools. You need to get them with PrintToolConfigAlg.
140
141 Args:
142 sequence (AnaAlgorithm.AlgSequence: the algorithm sequence to parse
143 output_dict (dict): the dictionary to populate
144 """
145 # Ensure output_dict is a dictionary
146 if not isinstance(output_dict, dict):
147 raise ValueError("output_dict must be a dictionary")
148
149 # Handle recursive case for AlgSequence
150 if isinstance(sequence, AlgSequence):
151 for alg in sequence:
152 nested_dict = {}
153 save_algs_from_sequence_ELjob(alg, nested_dict)
154 output_dict[alg.name()] = nested_dict
155 else:
156 # Handle the algorithm itself
157 _save_algorithm(sequence, output_dict)
158
159
160def _save_algorithm(sequence, output_dict):
161 """ Helper function to save individual algorithm properties """
162 output_dict['Type'] = sequence.type()
163 output_dict['Properties'] = {'Tools': {}}
164
165 for key, value in sorted(sequence._props.items()):
166 if isinstance(value, str):
167 output_dict['Properties'][key] = value
168 elif isinstance(value, PrivateToolConfig):
169 _handle_tool_config(value, output_dict)
170 else:
171 _safe_add_property(key, value, output_dict)
172
173
174def _handle_tool_config(tool_config, output_dict):
175 """ Handle tool configurations inside algorithms """
176 log = logging.getLogger("SaveConfigUtils")
177 if tool_config._prefix in output_dict['Properties']['Tools']:
178 log.warning(f"Already have this tool in the dict, prefix: {tool_config._prefix}. You need to ID the name somehow.")
179 else:
180 output_dict['Properties']['Tools'][tool_config._prefix] = {'Type': tool_config._type}
181
182
183def _safe_add_property(key, value, output_dict):
184 """ Safely adds properties to the output_dict with error handling """
185 log = logging.getLogger("SaveConfigUtils")
186 try:
187 output_dict['Properties'][key] = value
188 except Exception as e:
189 log.warning(f"Couldn't add property '{key}' to dict. Value: {value}. Error: {e}")
STL class.
_register_settings(config, path, name, settings)
_safe_add_property(key, value, output_dict)
_save_algorithm(sequence, output_dict)
combine_tools_and_algorithms_ELjob(combine_dictionaries=True, text_file='tool_config.txt', alg_file='alg_sequence.json', output_file='my_analysis_config.json')
_handle_tool_config(tool_config, output_dict)
save_algs_from_sequence_ELjob(sequence, output_dict)
_register_new_tool(config, path, tool_id, name)