Belle II Software  release-05-01-25
pickle_path.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 """
5 basf2.pickle_path - Functions necessary to pickle and unpickle a Path
6 =====================================================================
7 
8 This module contains all the functiones necessary to serialize and deserialize
9 a full path with all modules, parameters, sub paths, conditions and so on. This
10 can be used in conjunction with ``basf2 --dump-path`` and ``basf2
11 --execute-path`` to save a full configuration to file and execute it later.
12 """
13 
14 import pybasf2
15 import pickle as _pickle
16 import os as _os
17 import sys as _sys
18 
19 
20 def serialize_value(module, parameter):
21  if parameter.name == 'path' and module.type() == 'SubEvent':
22  return serialize_path(parameter.values)
23  else:
24  return parameter.values
25 
26 
27 def deserialize_value(module, parameter_state):
28  if parameter_state['name'] == 'path' and module.type() == 'SubEvent':
29  return deserialize_path(parameter_state['values'])
30  else:
31  return parameter_state['values']
32 
33 
34 def serialize_conditions(module):
35  condition_list = []
36 
37  for condition in module.get_all_conditions():
38  condition_list.append({'value': condition.get_value(),
39  'operator': int(condition.get_operator()),
40  'path': serialize_path(condition.get_path()),
41  'option': int(condition.get_after_path())})
42 
43  return condition_list
44 
45 
46 def deserialize_conditions(module, module_state):
47  conditions = module_state['condition']
48  for cond in conditions:
49  module.if_value(str(pybasf2.ConditionOperator.values[cond['operator']]) + str(cond['value']),
50  deserialize_path(cond['path']), pybasf2.AfterConditionPath.values[cond['option']])
51 
52 
53 def serialize_module(module):
54  if module.type() == '' or module.type() == 'PyModule':
55  raise RuntimeError("Module '%s' doesn't have a type or is a Python module! Note that --dump-path cannot work"
56  "properly with basf2 modules written in Python." % (module.name()))
57  return {
58  'name': module.name(),
59  'type': module.type(),
60  'flag': module.has_properties(pybasf2.ModulePropFlags.PARALLELPROCESSINGCERTIFIED),
61  'parameters': [{'name': parameter.name, 'values': serialize_value(module, parameter)}
62  for parameter in module.available_params()
63  if parameter.setInSteering or module.type() == 'SubEvent'],
64  'condition': serialize_conditions(module) if module.has_condition() else None}
65 
66 
67 def deserialize_module(module_state):
68  module = pybasf2._register_module(module_state['type'])
69  module.set_name(module_state['name'])
70  if 'condition' in module_state and module_state['condition'] is not None:
71  deserialize_conditions(module, module_state)
72  if 'flag' in module_state and module_state['flag']:
73  # for some modules, this flag might be changed from the default
74  module.set_property_flags(pybasf2.ModulePropFlags.PARALLELPROCESSINGCERTIFIED)
75  for parameter_state in module_state['parameters']:
76  module.param(parameter_state['name'],
77  deserialize_value(module, parameter_state))
78  return module
79 
80 
81 def serialize_path(path):
82  return {'modules': [serialize_module(module) for module in path.modules()]}
83 
84 
85 def deserialize_path(path_state):
86  path = pybasf2.Path()
87  for module_state in path_state['modules']:
88  module = deserialize_module(module_state)
89  path.add_module(module)
90  return path
91 
92 
93 def get_path_from_file(path_filename):
94  """Read a path from a given pickle file"""
95  with open(path_filename, 'br') as f:
96  return deserialize_path(_pickle.load(f))
97 
98 
99 def write_path_to_file(path, filename):
100  """Write a path to a given pickle file"""
101  with open(filename, 'bw') as f:
102  _pickle.dump(serialize_path(path), f)
103 
104 
105 def check_pickle_path(path):
106  """Check if the path to be executed should be pickled or unpickled.
107  This function is used by basf2.process to handle the ``--dump-path`` and
108  ``--execute-path`` arguments to ``basf2``
109  """
110  # If a pickle path is set via --dump-path or --execute-path we do something special
111  pickle_filename = pybasf2.get_pickle_path()
112  if pickle_filename == '':
113  return path
114 
115  # If the given path is None and the picklePath is valid we load a path from the pickle file
116  if _os.path.isfile(pickle_filename) and path is None:
117  path = get_path_from_file(pickle_filename)
118  with open(pickle_filename, "br") as f:
119  loaded = _pickle.load(f)
120  if 'state' in loaded:
121  pybasf2.B2INFO("Pickled path contains a state object. Activating pickled state.")
122  for name, args, kwargs in loaded['state']:
123  getattr(_sys.modules[__name__], name)(*args, **kwargs)
124  return path
125 
126  # Otherwise we dump the given path into the pickle file and exit
127  elif path is not None:
128  write_path_to_file(path, pickle_filename)
129  return None
130  else:
131  pybasf2.B2FATAL("Couldn't open path-file '" + pickle_filename + "' and no steering file provided.")
132 
133 
134 def make_code_pickable(code):
135  """
136  Sometimes it is necessary to execute code which won't be pickled if a user dumps the basf2 path
137  and wants to execute it later. Using the pickable_basf2 module all calls to basf2 functions
138  are recorded. Now if a user has to execute code outside of basf2, e.g. modifying objects in the ROOT namespace,
139  this won't be pickled. By wrapping the code in this function it is technically a call to a basf2 function
140  and will be pickled again. Problem solved.
141  """
142  exec(code, globals())