Belle II Software development
utilities.py
1
8
9import basf2
10
11import os
12import os.path
13import sys
14import argparse
15import collections
16
17
18class ArgumentParser(argparse.ArgumentParser):
19
20 """An argparse.Argument parse slightly changed such
21 that it always prints an extended help message increase of a parsing error."""
22
23 def error(self, message):
24 """Method invoked when a parsing error occurred.
25 Writes an extended help over the base ArgumentParser.
26 """
27 self.print_help()
28 sys.stderr.write(f'error: {message}\n')
29 sys.exit(2)
30
31
32class NonstrictChoices(list):
33
34 """Class that instances can be given to an argparse.ArgumentParser.add_argument as choices keyword argument.
35
36 The explicit choices stated during construction of this object are just suggestions but all other values are
37 excepted as well.
38 """
39
40 def __contains__(self, value):
41 """Test for correctness of the choices.
42 Always returns true since all choices should be valid not only the ones stated at construction of this object.
43 """
44 return True
45
46 def __iter__(self):
47 """Displays all explicit values and a final "..." to indicate more choices might be possible."""
48 # Append an ellipses to indicate that there are more choices.
49 copy = list(super().__iter__())
50 copy.append('...')
51 return iter(copy)
52
53 def __str__(self):
54 """Displays all explicit values and a final "..." to indicate more choices might be possible."""
55 # Append an ellipses to indicate that there are more choices.
56 copy = list(self)
57 copy.append('...')
58 return str(copy)
59
60
61def find_file(file_path):
62 # dec_file_path = Belle2.FileSystem.findFile(generator_module)
63 belle2_local_dir = os.environ.get("BELLE2_LOCAL_DIR", None)
64 if belle2_local_dir:
65 local_file_path = os.path.join(belle2_local_dir, file_path)
66 if os.path.exists(local_file_path) and os.path.isfile(local_file_path):
67 return local_file_path
68
69 belle2_central_dir = os.environ.get("BELLE2_RELEASE_DIR", None)
70 if belle2_central_dir:
71 central_file_path = os.path.join(belle2_central_dir, file_path)
72 if os.path.exists(central_file_path) and os.path.isfile(central_file_path):
73 return central_file_path
74
75 if os.path.exists(file_path) and os.path.isfile(file_path):
76 return file_path
77
78 return None
79
80
81def extend_path(path,
82 module,
83 module_by_short_name={},
84 allow_function_import=False):
85 """Convenience adder function that can resolve additional short hand module names from a dictionary"""
86 if isinstance(module, basf2.Module):
87 # A module instance
88 path.add_module(module)
89
90 elif isinstance(module, str):
91 # A module name of a short name as defined in the forwarded dictionary
92 if allow_function_import:
93 if "." in module:
94 # Allow imports from the local directory
95 sys.path.append(os.getcwd())
96
97 py_module_name, function_name = module.rsplit(".", 1)
98 try:
99 import importlib
100 py_module = importlib.import_module(py_module_name)
101 except ImportError:
102 pass
103 else:
104 py_function = getattr(py_module, function_name)
105 py_function(path)
106 return
107
108 if module in module_by_short_name:
109 short_name = module
110 module = module_by_short_name[short_name]
111 # module is a short name
112 # resolve it and add it
113 extend_path(path, module, module_by_short_name)
114 else:
115 # module is a module name from basf2
116 path.add_module(module)
117
118 elif callable(module):
119 # A convenience function or a module class
120 try:
121 if issubclass(module, basf2.Module):
122 # module is a module class
123 # create an instance and add it to the path
124 module_instance = module()
125 path.add_module(module_instance)
126 return
127 except TypeError:
128 pass
129
130 # module is not a module class
131 # try it as a convenience function that add modules to the path
132 module(path)
133
134 elif isinstance(module, collections.abc.Iterable):
135 # A list of modules or basf2.Path
136 modules = module
137 for module in modules:
138 extend_path(path, module, module_by_short_name)
139 else:
140 message_template = """
141'%s of type %s is neither
142* a module instance
143* a module (python) class
144* a module name
145* a add_* function
146* a short name resolvable from %s.'
147* an iterable of the above (e.g. basf2.Path)
148"""
149 raise ValueError(message_template % (module,
150 type(module),
151 module_by_short_name.keys()))
152
153
154def get_module_param(module, name):
155 parameters = module.available_params()
156 for parameter in parameters:
157 if name == parameter.name:
158 return parameter.values
159 else:
160 raise AttributeError(f'{module} module does not have a parameter named {name}')