Belle II Software  release-05-01-25
_constwrapper.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 """
5 Modify the PyDBObj and PyDBArray classes to return read only objects to prevent
6 accidental changes to the conditions data.
7 
8 This module does not contain any public functions or variables, it just
9 modifies the ROOT interfaces for PyDBObj and PyDBArray to only return read only
10 objects.
11 """
12 
13 
14 # we need to patch PyDBObj so lets import the ROOT cpp backend.
15 import cppyy as _cppyy
16 # However importing ROOT.kIsConstMethod and kIsStatic is a bad idea
17 # here since it triggers final setup of ROOT and thus starts a gui thread
18 # (probably) and consumes command lines if not disabled *sigh*. So we take them
19 # as literal values from TDictionary.h. We do have a unit test to make sure
20 # they don't change silently though.
21 
22 
23 _ROOT_kIsStatic = 0x00004000
24 
25 _ROOT_kIsConstMethod = 0x10000000
26 
27 
29  """Empty class to check whether an instance is already const wrapped"""
30  pass
31 
32 
33 def _make_tobject_nonconst(obj):
34  """Make a once read only TObject writeable again"""
35  if isinstance(obj, _TObjectConstWrapper):
36  object.__setattr__(obj, "__class__", obj.__real_class__)
37  del obj.__real_class__
38  return obj
39 
40 
41 def _make_tobject_const(obj):
42  """
43  Make a TObject const: Disallow setting any attributes and calling any
44  methods which are not declared as const. This affects any reference to this
45  object in python and stays permanent. Once called this particular instance
46  will be readonly everywhere and will remain like this.
47 
48  This is done by modifying the __class__ attribute of the object and replace
49  it with a new subclass which hides all non-const, non-static member
50  functions and disable attribute setting. The reason we do it like this is
51  that this is the only way how comparison and copy constructor calls work
52  transparently. E.g with a normal instance of type T 'a' and a const wrapped
53  T 'b' we can do things like a == b, b == a, a < b, b > a, c = T(b).
54  """
55  # nothing to do if already const wrapped or None
56  if obj is None or isinstance(obj, _TObjectConstWrapper):
57  return obj
58 
59  # ROOT 6.14/04 issues a runtime warning for GetListOfAllPublicMethods() but
60  # the code still seems to work fine (ROOT-9699)
61  import warnings
62  try:
63  with warnings.catch_warnings():
64  warnings.simplefilter("ignore")
65 
66  non_const = [m.GetName() for m in obj.Class().GetListOfAllPublicMethods()
67  if not (m.Property() & (_ROOT_kIsConstMethod | _ROOT_kIsStatic))]
68  except AttributeError:
69  raise ValueError("Object does not have a valid dictionary: %r" % obj)
70 
71  def __make_const_proxy(obj, name):
72  """return a proxy function which just raises an attribute error on access"""
73 
74  def proxy(self, *args):
75  """raise attribute error when called"""
76  raise AttributeError("%s is readonly and method '%s' is not const" % (obj, name))
77  return proxy
78 
79  def __setattr(self, name, value):
80  """disallow setting of any attributes"""
81  raise AttributeError("%s is readonly, can't set attribute" % obj)
82 
83  # ok we create a derived class which removes access to non-const, non-static member
84  # functions by adding properties for them which will always cause
85  # AttributeErrors
86  scope = {m: property(__make_const_proxy(obj, m)) for m in non_const}
87  # Also add a __setattr__ to make sure nobody changes anything
88  scope["__setattr__"] = __setattr
89  # create a dynamic type for this which inherits from the original type and
90  # _TObjectConstWrapper so we can distuingish it later. Add all our proxies
91  # as scope
92  normal_type = type(obj)
93  const_type = type("const %s" % normal_type.__name__, (normal_type, _TObjectConstWrapper), scope)
94  # remember the old class
95  obj.__real_class__ = normal_type
96  # and sneakily replace the class of this object :D
97  obj.__class__ = const_type
98  # done
99  return obj
100 
101 
102 def _PyDBArray__iter__(self):
103  """Provide iteration over Belle2::PyDBArray. Needs to be done here since we
104  want to make all returned classes read only"""
105  for i in range(len(self)):
106  yield self[i]
107 
108 
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 _dbobj_scope = _cppyy._backend.CreateScopeProxy("Belle2::PyDBObj")
112 _dbobj_scope.obj = lambda self: _make_tobject_const(self._obj())
113 # and allow to use it most of the time without calling obj() like the ->
114 # indirection in C++
115 _dbobj_scope.__getattr__ = lambda self, name: getattr(self.obj(), name)
116 # also make item access in DBArray readonly
117 _dbarray_scope = _cppyy._backend.CreateScopeProxy("Belle2::PyDBArray")
118 _dbarray_scope.__getitem__ = lambda self, i: _make_tobject_const(self._get(i))
119 # and supply an iterator
120 _dbarray_scope.__iter__ = _PyDBArray__iter__
121 # and make sure that if we can iterate over the items in the class pointed to
122 # by the StoreObjPtr or DBObjPtr it allows iteration
123 _dbobj_scope.__iter__ = lambda self: iter(self.obj())
124 _storeobj_scope = _cppyy._backend.CreateScopeProxy("Belle2::PyStoreObj")
125 _storeobj_scope.__iter__ = lambda self: iter(self.obj())
basf2._constwrapper._TObjectConstWrapper
Definition: _constwrapper.py:28