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