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