ATLAS Offline Software
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 """
5 This module defines utilities for the jet config.
6 These are mainly to allow to "lock" the standard, reference definitions so
7 they are not accidentally changed by some user scripts.
8 """
9 
10 class 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 
20 def 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 
55 def 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 
114 def 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 
139 class 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 
173 def 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
python.Utilities.listClassLProperties
def listClassLProperties(cls)
Definition: Utilities.py:63
python.Utilities.make_alias
def make_alias(prop)
Definition: Utilities.py:55
python.Utilities.ldict.update
def update(self, **args)
Definition: Utilities.py:160
python.Utilities.ldict.__init__
def __init__(self, **args)
Definition: Utilities.py:146
python.Utilities.clone
clone
Definition: Utilities.py:134
python.Utilities.make_lproperty
def make_lproperty(func)
Definition: Utilities.py:20
python.Utilities.ldict.clone
def clone(self, **args)
Definition: Utilities.py:164
python.Utilities.lproperty
Definition: Utilities.py:10
python.Utilities.onlyAttributesAreProperties
def onlyAttributesAreProperties(cls)
Definition: Utilities.py:75
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.Utilities.clonable
def clonable(cls)
Definition: Utilities.py:114
python.Utilities.__setattr__
__setattr__
Definition: Utilities.py:110
python.Utilities.lproperty.lsetter
def lsetter(self, func)
Definition: Utilities.py:12
python.Utilities.ldict
Definition: Utilities.py:139
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
str
Definition: BTagTrackIpAccessor.cxx:11
python.Utilities.flattenDic
def flattenDic(inD)
Definition: Utilities.py:173
python.Utilities.ldict.__setitem__
def __setitem__(self, k, v)
Definition: Utilities.py:152