Belle II Software light-2505-deimos
_constwrapper.py
1#!/usr/bin/env python3
2
3
10
11"""
12Modify the PyDBObj and PyDBArray classes to return read only objects to prevent
13accidental changes to the conditions data.
14
15This module does not contain any public functions or variables, it just
16modifies the ROOT interfaces for PyDBObj and PyDBArray to only return read only
17objects.
18"""
19
20# we need to patch PyDBObj so lets import the ROOT cppyy backend.
21import cppyy
22
23# However importing ROOT.kIsConstMethod and kIsStatic is a bad idea here since
24# it triggers final setup of ROOT and thus starts a gui thread (probably).
25# So we take them as literal values from TDictionary.h. We do have a unit test
26# to make sure they don't change silently though.
27
28
29_ROOT_kIsPublic = 0x00000200
30
31_ROOT_kIsStatic = 0x00004000
32
33_ROOT_kIsConstMethod = 0x10000000
34
35
36# copy and enhanced version of ROOT.pythonization
37def pythonization(lazy=True, namespace=""):
38 """
39 Pythonizor decorator to be used in pythonization modules for pythonizations.
40 These pythonizations functions are invoked upon usage of the class.
41 Parameters
42 ----------
43 lazy : boolean
44 If lazy is true, the class is pythonized upon first usage, otherwise
45 upon import of the ROOT module.
46 """
47 def pythonization_impl(fn):
48 """
49 The real decorator. This structure is adopted to deal with parameters
50 fn : function
51 Function that implements some pythonization.
52 The function must accept two parameters: the class
53 to be pythonized and the name of that class.
54 """
55 if lazy:
56 cppyy.py.add_pythonization(fn, namespace)
57 else:
58 fn()
59 return pythonization_impl
60
61
62def _make_tobject_const(obj):
63 """
64 Make a TObject const: Disallow setting any attributes and calling any
65 methods which are not declared as const. This affects any reference to this
66 object in python and stays permanent. Once called this particular instance
67 will be readonly everywhere and will remain like this.
68
69 This is done by replacing all setter functions with function objects that
70 just raise an attribute error when called. The class will be still the same
71 """
72 # nothing to do if None
73 if obj is None:
74 return obj
75
76 try:
77
78 non_const = [m.GetName() for m in obj.Class().GetListOfMethods() if (m.Property() & _ROOT_kIsPublic)
79 and not (m.Property() & (_ROOT_kIsConstMethod | _ROOT_kIsStatic))]
80 except AttributeError:
81 raise ValueError(f"Object does not have a valid dictionary: {obj!r}")
82
83 # Override all setters to just raise an exception
84 for name in non_const:
85 def __proxy(*args, **argk):
86 """raise attribute error when called"""
87 raise AttributeError(f"{obj} is readonly and method '{name}' is not const")
88
89 setattr(obj, name, __proxy)
90
91 # and return the modified object
92 return obj
93
94
95def _PyDBArray__iter__(self):
96 """Provide iteration over Belle2::PyDBArray. Needs to be done here since we
97 want to make all returned classes read only"""
98 for i in range(len(self)):
99 yield self[i]
100
101
102@pythonization(namespace="Belle2")
103def _pythonize(klass, name):
104 """Adjust the python interface of some Py* classes"""
105 if not name.startswith("Py"):
106 return
107
108 if name == "PyDBObj":
109 # now replace the PyDBObj getter with one that returns non-modifiable objects.
110 # This is how root does it in ROOT.py so let's keep that
111 klass.obj = lambda self: _make_tobject_const(self._obj())
112 # and allow to use it most of the time without calling obj() like the ->
113 # indirection in C++
114 klass.__getattr__ = lambda self, name: getattr(self.obj(), name)
115 # and make sure that we can iterate over the items in the class
116 # pointed to if it allows iteration
117 klass.__iter__ = lambda self: iter(self.obj())
118 elif name == "PyDBArray":
119 # also make item access in DBArray readonly
120 klass.__getitem__ = lambda self, i: _make_tobject_const(self._get(i))
121 # and supply an iterator
122 klass.__iter__ = _PyDBArray__iter__
123 elif name == "PyStoreObj":
124 # make sure that we can iterate over the items in the class
125 # pointed to if it allows iteration
126 klass.__iter__ = lambda self: iter(self.obj())