ATLAS Offline Software
Loading...
Searching...
No Matches
Utilities.py
Go to the documentation of this file.
1# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2
3
4"""
5This module defines utilities for the jet config.
6These are mainly to allow to "lock" the standard, reference definitions so
7they are not accidentally changed by some user scripts.
8"""
9
10class lproperty(property):
11 """Build on the standard property to allow a property to be locked if the holding class has its _locked attribute set to True """
12 def lsetter(self, func):
13 def lockedfunc(self, v):
14 if self._locked:
15 raise Exception("Error "+func.__name__+" is locked. Either clone or unlock",self)
16 func(self,v)
17
18 return self.setter(lockedfunc)
19
20def make_lproperty( func ):
21 """creates a property from a class method (or a str) which can be locked if the holding class has its _locked attribute set to True
22
23 usage :
24 class A:
25 # simple locked property
26 x = make_lproperty('x')
27
28 # same but using decorator
29 @make_lproperty
30 def y(self): pass
31
32 # same but now with customized setter :
33 @make_lproperty
34 def z(self): pass
35 @z.lsetter
36 def z(self, v):
37 print("setting z to ",v)
38 self._z = v
39
40 """
41 if isinstance(func, str):
42 pname = func
43 else:
44 pname = func.__name__
45 pname_i = '_'+pname
46
47 def _getter(self):
48 return getattr(self,pname_i, None)
49 def _setter(self, v):
50 if self._locked:
51 raise AttributeError("Error property '"+pname+"' is locked. Either clone or unlock",self)
52 setattr(self, pname_i, v)
53 return lproperty(_getter, _setter)
54
55def make_alias( prop ):
56 """Returns a property which act as a read-only alias to existing prop """
57 def _getter(self):
58 return getattr(self,prop,None)
59 def _setter(self,v):
60 raise AttributeError("Cannot set alias "+prop+" from "+str(self) )
61 return property(_getter, _setter)
62
64 # # if cls has a predefined _allowedattributes, use it, else start with []
65 lprops = getattr(cls, '_allowedattributes', [])
66 # allow all lproperty attached to cls
67 lprops += [k for (k,v) in cls.__dict__.items() if isinstance(v,lproperty) ]
68 # and the corresponding internal _xyz members
69 lprops +=[ '_'+k for k in lprops]
70 # also allow all what is allowed from the base classes
71 for base in cls.__bases__:
72 lprops += listClassLProperties(base)
73 return lprops
74
76 """Transforms the input class cls so the only attributes which can be set are the lproperty of the class.
77 Best used as a decorator. Ex :
78 @onlyAttributesAreProperties
79 class A:
80 myprop0 = make_lproperty('myprop0')
81
82 a = A()
83 a.myprop0 = 0 # ok
84 a.mypropO = 3 # impossible
85 """
86 # build the list of attributes allowed to be set : these are the properties and _locked
87 #cls._allowedattributes = [k for (k,v) in cls.__dict__.items() if isinstance(v,lproperty) ]
88 #cls._allowedattributes +=[ '_'+k for k in cls._allowedattributes]
89 cls._allowedattributes = listClassLProperties( cls )
90 cls._allowedattributes += ['_locked']
91
92 # flag to activate the prevention of adding new attributes. we set it at the end of __init__
93 cls._nomoreattributes=False
94 cls._locked = False
95
96 cls.__init__origin = cls.__init__
97
98 # define a new __init__ for this class.
99 # the 'finalinit' argument allows to avoid locking the allowed attribute : this is to be used when a derived class wants to call the init of it's base class.
100 def initwraper(self, *l,finalinit=True, **args):
101 cls.__init__origin(self, *l,**args)
102 self._nomoreattributes = finalinit
103 cls.__init__ = initwraper
104
105 # define a __setattr__ for this class
106 def setattr(self, k, v):
107 if self._nomoreattributes and k not in self._allowedattributes:
108 raise AttributeError("Setting attribute "+k+" on "+str(self)+" not allowed")
109 super(cls,self).__setattr__(k,v)
110 cls.__setattr__ = setattr
111
112 return cls
113
114def clonable(cls):
115 """Transforms the input class cls by adding a clone() method.
116 This clone() method returns a clone instance with a _locked attribute set to False by default (so the clone is modifiable) """
117
118 # Only unlock other instances of clonable
119 # This can otherwise do nasty things like unlocking AthConfigFlags!
120 def recc_lock(o, lock):
121 if hasattr(type(o),'isClonable') and type(o).isClonable and hasattr(o,"_locked"):
122 o._locked = lock
123 for k,v in o.__dict__.items():
124 recc_lock(v,lock)
125
126 def clone(self, lock=False, **args):
127 from copy import deepcopy
128 o = deepcopy(self)
129 o._locked = False # unlock so we can modfiy the clone with user given arguments
130 for k,v in args.items():
131 setattr(o,k,v)
132 recc_lock(o,lock) # make sure lock is propagated to all lockable sub-object
133 return o
134 cls.clone = clone
135 cls.isClonable = True
136 return cls
137
138
139class ldict(dict):
140 """A dictionnary which items can not be modified once set.
141
142 Also implements 2 additional features :
143 - a clone method to conveniently create a new version with only some items changed
144 - all items are also attributes (i.e. d['a'] can be accessed by d.a : this is mostly for easier interactive inspection)
145 """
146 def __init__(self, **args):
147 super().__init__(**args)
148 # make key aivailable as attribute
149 for k,v in args.items():
150 super().__setattr__(k,v)
151
152 def __setitem__(self, k, v):
153 if k in self:
154 if v!=self[k]:
155 raise KeyError(f"Can't override self[{k}] == {self[k]} with new value {v}")
156 else:
157 super().__setitem__(k,v)
158 super().__setattr__(k,v)
159
160 def update(self, **args): # we need to redefine it
161 for k,v in args.items():
162 self[k]=v
163
164 def clone(self,**args):
165 from copy import deepcopy
166 o = deepcopy(self)
167 for k,v in args.items():
168 # bypass the protection to update the user given items
169 dict.__setitem__(o,k,v)
170 dict.__setattr__(o,k,v)
171 return o
172
173def flattenDic(inD):
174 """returns a copy of the inD dictionnary with no nested directories. Instead the content of nested dict inside inD are indexed by keys constructed from nested keys :
175 {'A' : 3 , 'B' : { 'C':5, 'D': 6} } --> {'A' : 3 , 'B.C':5, 'B.D': 6}
176 This works only if nested dic have str keys.
177
178 This function is used mainly in trigger config
179 """
180 outD = {}
181 for k,v in inD.items():
182 if isinstance(v,dict):
183 subD = flattenDic(v)
184 for subk, subv in subD.items():
185 outD[f'{k}.{subk}']=subv
186 else:
187 outD[k]=v
188 return outD
__setitem__(self, k, v)
Definition Utilities.py:152
update(self, **args)
Definition Utilities.py:160
make_lproperty(func)
Definition Utilities.py:20
onlyAttributesAreProperties(cls)
Definition Utilities.py:75
listClassLProperties(cls)
Definition Utilities.py:63