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