Belle II Software  light-2205-abys
datastoreprinter.py
1 #!/usr/bin/env python3
2 
3 
10 
11 from ROOT import Belle2, kIsPublic, kIsStatic, TVector3, TLorentzVector
12 from ROOT.Math import XYZVector
13 from basf2 import Module, B2FATAL
14 
15 
16 def get_public_members(classname):
17  """
18  Return a list of public, non-static member functions for a given classname.
19  The class must exist in the Belle2 namespace and have a ROOT dictionary
20  """
21  tclass = getattr(Belle2, classname).Class()
22  members = {e.GetName() for e in tclass.GetListOfMethods()
23  if (e.Property() & kIsPublic) and not (e.Property() & kIsStatic)}
24 
25  # filter known members from ClassDef and constructor/destructor
26  # Remove some Dictionary members
27  removed = {
28  "CheckTObjectHashConsistency", "Class", "Class_Name", "Class_Version",
29  "DeclFileLine", "DeclFileName", "Dictionary", "ImplFileLine",
30  "ImplFileName", "IsA", "ShowMembers", "Streamer", "StreamerNVirtual",
31  "operator!=", "operator=", "operator==",
32  # we don't need constructor and destructor either
33  classname, f"~{classname}",
34  }
35  members -= removed
36  return list(sorted(members))
37 
38 
40  """
41  Class to print contents of a StoreObjPtr or StoreArray.
42 
43  This class is inteded to print the contents of dataobjects to the standard
44  output to monitor changes to the contents among versions.
45 
46  For example:
47 
48  >>> printer = DataStorePrinter("MCParticle", ["getVertex"], {"hasStatus": [1, 2, 4]})
49  >>> printer.print()
50 
51  will loop over all MCParticle instances in the MCParticles StoreArray and
52  print someting like ::
53 
54  MCParticle#0
55  getVertex(): (0,0,0)
56  hasStatus(1): True
57  hasStatus(2): False
58  hasStatus(4): False
59 
60  for each MCparticle
61  """
62 
63  def __init__(self, name, simple, withArgument=None, array=True):
64  """
65  Initialize
66 
67  Args:
68  name (str): class name of the DataStore object
69  simple (list): list of member names to print which do not need any additional
70  arguments
71  withArgument (dict or None): dictionary of member names and a list of
72  all argument combinations to call them.
73  array (bool): if True we print a StoreArray, otherwise a single StoreObjPtr
74  """
75 
76  self.namename = name
77 
78  self.arrayarray = array
79 
80  self.object_membersobject_members = []
81 
82  # add the simple members to the list of members to call with empty
83  # arguments
84  for member in simple:
85  self.object_membersobject_members.append((member, [], None, None))
86 
87  # and add the members with Argument
88  if withArgument:
89  for member, arguments in withArgument.items():
90  for args in arguments:
91  # convert args to a list
92  if not isinstance(args, list) or isinstance(args, tuple):
93  args = [args]
94  # and add (name,args,display,callback) tuple
95  self.object_membersobject_members.append((member, args, None, None))
96 
97  # sort them by member name to have fixed order: python sort is
98  # guaranteed to be stable so different calls to the same member will
99  # remain in same relative order
100  self.object_membersobject_members.sort(key=lambda x: x[0])
101 
102  def add_member(self, name, arguments=None, print_callback=None, display=None):
103  """
104  Add an additional member to be printed.
105 
106  Args:
107  name (str): name of the member
108  arguments (list or callable): arguments to pass to the member when calling it
109  If this is a callable object then the function is called with
110  the object as first argument and the member name to be tested as
111  second argument. The function is supposed to return the list of
112  arguments to pass to the member when calling. Possible return
113  valus for the callable are:
114 
115  * a `list` of arguments to be passed to the member. An empty
116  `list` means to call the member with no arguments.
117  * a `tuple` of `lists <list>` to call the member once for each
118  list of arguments in the tuple
119  * `None` or an empty tuple to not call the member at all
120  print_callback (function or None): if not None a function to print
121  the result of the member call. The function will be called with
122  the arguments (name, arguments, result) and should print the
123  result on stdout without any additional information.
124  display (str or None): display string to use when printing member call
125  info instead of function name + arguments. If it is not given
126  the default output will be ``{membername}({arguments}):``
127  followed by the result.
128  """
129  if arguments is None:
130  arguments = []
131  self.object_membersobject_members.append((name, arguments, print_callback, display))
132  # return self for method chaining
133  return self
134 
135  def print(self):
136  """Print all the objects currently existing"""
137  if self.arrayarray:
138  data = Belle2.PyStoreArray(self.namename + "s")
139  for i, obj in enumerate(data):
140  self._printObj_printObj(obj, i)
141  else:
142  obj = Belle2.PyStoreObj(self.namename)
143  if obj:
144  self._printObj_printObj(obj.obj())
145 
146  def print_untested(self):
147  """Print all the public member functions we will not test"""
148  members = get_public_members(self.namename)
149  tested = {e[0] for e in self.object_membersobject_members}
150  for member in members:
151  if member in tested:
152  continue
153  print(f"Untested method {self.name}::{member}")
154 
155  def _printObj(self, obj, index=None):
156  """Print all defined members for each object with given index.
157  If we print a StoreObjPtr then index is None and this function is called
158  once. If we print StoreArrays this function is called once for each
159  entry in the array with index set to the position in the array
160  """
161  # print array index? If yes then add it to the output
162  if index is not None:
163  index = "#%d" % index
164  else:
165  index = ""
166 
167  print(f"{self.name}{index}:")
168 
169  # loop over all defined member/argument combinations
170  # and print "member(arguments): result" for each
171  for name, arguments, callback, display in self.object_membersobject_members:
172  # if arguments is callable it means we need to call it to determine
173  # the arguments
174  if callable(arguments):
175  all_args = arguments(obj, name)
176  # None means we don't calle the member this time
177  if all_args is None:
178  continue
179  # list is one set of arguments, tuple(list) is n set of
180  # arguments. So convert list into tuple of length 1 to call
181  # member once
182  if isinstance(all_args, list):
183  all_args = (all_args,)
184  else:
185  # if arguments is not callable we asume it's one set of
186  # arguments
187  all_args = (arguments,)
188 
189  for args in all_args:
190  result = getattr(obj, name)(*args)
191  # display can be used to override what is printed for the member. If
192  # so, use it here
193  if display is not None:
194  print(" " + display + ": ", end="")
195  else:
196  # otherwise just print name and arguments
197  print(" {}({}): ".format(name, ",".join(map(str, args))), end="")
198  # if a callback is set the callback is used to print the result
199  if callback is not None:
200  print("", end="", flush=True)
201  callback(name, args, result)
202  # otherwise use default function
203  else:
204  self._printResult_printResult(result)
205 
206  def _printResult(self, result, depth=0, weight=None):
207  """ Print the result of calling a certain member.
208  As we want the test to be independent of memory we have to be a bit careful
209  how to not just print() but check whether the object is maybe a pointer to another
210  DataStore object or if it is a TObject with implements Print().
211  Also, call recursively std::pair
212 
213  Args:
214  result: object to print
215  depth (int): depth for recursive printing, controls the level of indent
216  weight (float or None): weight to print in addition to object, only used for
217  relations
218  """
219  # are we in recursion? if so indent the output
220  if depth:
221  print(" " * (depth + 1), end="")
222 
223  if weight is not None:
224  weight = " (weight: %.6g)" % weight
225  else:
226  weight = ""
227 
228  # is it another RelationsObject? print array name and index
229  if hasattr(result, "getArrayName") and hasattr(result, "getArrayIndex"):
230  if not result:
231  print("-> NULL%s" % weight)
232  else:
233  print("-> %s#%d%s" % (result.getArrayName(), result.getArrayIndex(), weight))
234  # special case for TMatrix like types to make them more space efficient
235  elif hasattr(result, "GetNrows") and hasattr(result, "GetNcols"):
236  print(weight, end="")
237  for row in range(result.GetNrows()):
238  print("\n" + " " * (depth + 2), end="")
239  for col in range(result.GetNcols()):
240  print("%13.6e " % result(row, col), end="")
241 
242  print()
243  # or is it a TVector3 or TLorentzVector?
244  elif isinstance(result, TVector3):
245  print("(" + ",".join("%.6g" % result[i] for i in range(3)) + ")")
246  elif isinstance(result, XYZVector):
247  print("(" + ",".join("%.6g" % Belle2.B2Vector3D(result)[i] for i in range(3)) + ")")
248  # print("(" + ",".join("%.6g" % x for x in [result.X(), result.Y(), result.Z()]) + ")")
249  elif isinstance(result, TLorentzVector):
250  print("(" + ",".join("%.6g" % result[i] for i in range(4)) + ")")
251  # or, does it look like a std::pair?
252  elif hasattr(result, "first") and hasattr(result, "second"):
253  print("pair%s" % weight)
254  self._printResult_printResult(result.first, depth + 1)
255  self._printResult_printResult(result.second, depth + 1)
256  # or, could it be a std::vector like container? But ROOT might wrap a std::string so if it has npos assume it's a string
257  elif (hasattr(result, "size") and hasattr(result, "begin") and hasattr(result, "end")) and not hasattr(result, "npos"):
258  print("size(%d)%s" % (result.size(), weight))
259  # if it is a RelationVector we also want to print the weights. So
260  # check whether we have weights and pass them to the _printResult
261  weight_getter = getattr(result, "weight", None)
262  weight = None
263  # loop over all elements and print the elements with one level more
264  # indentation
265  for i, e in enumerate(result):
266  if weight_getter is not None:
267  weight = weight_getter(i)
268  self._printResult_printResult(e, depth + 1, weight=weight)
269  # print floats with 6 valid digits
270  elif isinstance(result, float):
271  print(f"{result:.6g}{weight}")
272  # print char as int
273  elif isinstance(result, str) and len(result) == 1:
274  print(ord(result), weight, sep="")
275  # ok, in any case we can just print it
276  else:
277  print(result, weight, sep="")
278 
279 
280 # ok, now we just need a small class to call all the printer objects for each
281 # event
282 class PrintObjectsModule(Module):
283 
284  """Call all DataStorePrinter objects in for each event"""
285 
286  def __init__(self, objects_to_print, print_untested=False):
287  """
288  Initialize
289 
290  Args:
291  objects_to_print (list): list of object to print
292  """
293 
294  self.objects_to_printobjects_to_print = objects_to_print
295 
296  self.print_untestedprint_untested = print_untested
297  super().__init__()
298 
299  def initialize(self):
300  """Print all untested members if requested"""
301  if not self.print_untestedprint_untested:
302  return
303 
304  for printer in self.objects_to_printobjects_to_print:
305  printer.print_untested()
306 
307  def event(self):
308  """print the contents of the mdst mdst_dataobjects"""
309  try:
310  for printer in self.objects_to_printobjects_to_print:
311  printer.print()
312  except Exception as e:
313  B2FATAL("Error in datastore printer: ", e)
A (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:72
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67
def __init__(self, name, simple, withArgument=None, array=True)
array
if True we print a StoreArray, otherwise a single StoreObjPtr
def add_member(self, name, arguments=None, print_callback=None, display=None)
def _printResult(self, result, depth=0, weight=None)
object_members
list of object members to call and print their results
name
class name of the datastore object
def __init__(self, objects_to_print, print_untested=False)