All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
PropertyProxy.py
Go to the documentation of this file.
1 # File: AthenaCommon/python/PropertyProxy.py
2 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov)
3 # Author: Martin Woudstra (Martin.Woudstra@cern.ch)
4 
5 ### data ---------------------------------------------------------------------
6 __all__ = [ 'PropertyProxy', 'GaudiHandlePropertyProxy', 'GaudiHandleArrayPropertyProxy' ]
7 
8 import os,glob
9 from GaudiKernel.GaudiHandles import *
10 from GaudiKernel import ConfigurableDb
11 
12 import logging
13 log = logging.getLogger( 'PropertyProxy' )
14 
15 # dictionary with configurable class : python module entries
16 #-->PM#from AthenaCommon import ConfigurableDb
17 
18 
19 def derives_from(derived,base):
20  """A string version of isinstance().
21  <derived> is either an object instance, or a type
22  <base> is a string containing the name of the base class (or <derived> class)"""
23  if not isinstance(derived,type): derived=type(derived)
24  if derived.__name__ == base: return True
25  for b in derived.__bases__:
26  if derives_from(b,base): return True
27 
28  return False
29 
30 def _isCompatible( tp, value ):
31  errmsg = "received an instance of %s, but %s expected" % (type(value),tp)
32 
33  if derives_from(value, 'PropertyReference'):
34  # TODO: implement type checking for references
35  return value # references are valid
36  if ( tp is str ):
37  if ( type(value) is str ) or derives_from(value, 'Configurable'):
38  # we can set string properties only from strings or configurables
39  return value
40  else:
41  raise ValueError( errmsg )
42  elif ( tp in [ list, tuple, dict ] ):
43  if ( type(value) is tp ):
44  # We need to check that the types match for lists, tuples and
45  # dictionaries (bug #34769).
46  return value
47  else:
48  raise ValueError( errmsg )
49  elif derives_from(tp, 'Configurable'):
50  return value
51  else:
52  # all other types: accept if conversion allowed
53  try:
54  dummy = tp( value )
55  except (TypeError,ValueError):
56  raise ValueError( errmsg )
57 
58  return dummy # in case of e.g. classes with __int__, __iter__, etc. implemented
59 
60 
61 class PropertyProxy( object ):
62  def __init__( self, descr, docString=None, default=None ):
63  self.history = {}
64  self.descr = descr
65  if docString:
66  self.__doc__ = docString
67  if default is not None:
68  self.default = default
69 
70 
71  def setDefault( self, value ):
72  self.__default = value
73 
74  def getDefault( self ):
75  return self.__default
76 
77  default = property( getDefault, setDefault )
78 
79  def fullPropertyName( self, obj ):
80  return (obj.getJobOptName() or obj.getName()) + '.' + self.descr.__name__
81 
82  def __get__( self, obj, type = None ):
83  try:
84  return self.descr.__get__( obj, type )
85  except AttributeError:
86  # special case for lists and dictionaries:
87  # allow default to work with on += and []
88  if self.__default.__class__ in [ list, dict ]:
89  self.descr.__set__( obj, self.__default.__class__(self.__default) )
90  return self.descr.__get__( obj, type )
91  else:
92  # for non lists (or dicts) return a reference to the default
93  #return self.__default
94  raise
95 
96  def __set__( self, obj, value ):
97  # check value/property compatibility if possible
98  proptype, allowcompat = None, False
99  if hasattr( self, 'default' ):
100  proptype = type(self.default)
101  if self.descr.__name__ == 'OutputLevel': # old-style compat for Btag
102  allowcompat = True
103  elif obj in self.history:
104  proptype = type( self.history[ obj ][ 0 ] )
105  allowcompat = True
106 
107  # check if type known; allow special initializer for typed instances
108  # Do not perform the check for PropertyReference, should be delayed until
109  # binding (if ever done)
110  if proptype and proptype != type(None) and \
111  not derives_from(value, 'PropertyReference'):
112  try:
113  # check value itself
114  value = _isCompatible( proptype, value )
115 
116  # check element in case of list
117  if proptype == list:
118  try:
119  oldvec = self.descr.__get__( obj, type )
120  if oldvec:
121  tpo = type(oldvec[0])
122  for v in value:
123  _isCompatible( tpo, v )
124  except AttributeError:
125  # value not yet set
126  pass
127  except ValueError, e:
128  if allowcompat:
129  log.error( 'inconsistent value types for %s.%s (%s)' %\
130  (obj.getName(),self.descr.__name__,str(e)) )
131  else:
132  raise
133 
134  # allow a property to be set if we're in non-default mode, or if it
135  # simply hasn't been set before
136  if not obj._isInSetDefaults() or not obj in self.history:
137  # by convention, 'None' for default is used to designate objects setting
138  if hasattr( self, 'default' ) and self.default == None:
139  obj.__iadd__( value, self.descr ) # to establish hierarchy
140  else:
141  self.descr.__set__( obj, value )
142  self.history.setdefault( obj, [] ).append( value )
143 
144  def __delete__( self, obj ):
145  if obj in self.history:
146  del self.history[ obj ]
147  self.descr.__delete__( obj )
148 
149 
150 
152  """A class with some utilities for GaudiHandles and GaudiHandleArrays"""
153 
154  def __init__(self, descr, docString, default, handleType, allowedType ):
155  """<descr>: the real property in the object instance (from __slots__)
156  <docString>: the documentation string of this property
157  <default>: default value from C++ (via python generated by genconf)
158  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
159  <allowedType>: allowed instance type for default
160  """
161  # check that default is of allowed type for this proxy
162  if not isinstance(default,allowedType):
163  raise TypeError( "%s: %s default: %r is not a %s" % \
164  ( descr.__name__, self.__class__.__name__, default, allowedType.__name__ ) )
165  PropertyProxy.__init__( self, descr, docString, default )
166  self._handleType = handleType
167  self._confTypeName = 'Configurable' + handleType.componentType
168 # print "%s: %r (%s)" % (self.__class__.__name__,self._handleType,self._confTypeName)
169 
170 
171  def __get__( self, obj, type = None ):
172  try:
173  return self.descr.__get__( obj, type )
174  except AttributeError:
175  # Get default
176  try:
177  default = obj.__class__.getDefaultProperty( self.descr.__name__ )
178  default = self.convertDefaultToBeSet( obj, default )
179  if default:
180  self.__set__( obj, default )
181  except AttributeError,e:
182  # change type of exception to avoid false error message
183  raise RuntimeError(*e.args)
184 
185  return self.descr.__get__( obj, type )
186 
187 
188  def __set__( self, obj, value ):
189  # allow a property to be set if we're in non-default mode, or if it
190  # simply hasn't been set before
191  if not obj._isInSetDefaults() or not obj in self.history:
192  value = self.convertValueToBeSet( obj, value )
193  # assign the value
194  self.descr.__set__( obj, value )
195  log.debug( "Setting %s = %r", self.fullPropertyName( obj ), value )
196  self.history.setdefault( obj, [] ).append( value )
197 
198 
199 
200  def isHandle(self,value):
201  """Check if <value> is a handle of the correct type"""
202  return isinstance(value,self._handleType)
203 
204 
205  def isConfig(self,value):
206  """Check if <value> is a configurable of the correct type"""
207  return derives_from(value,self._confTypeName)
208 
209 
210  def getDefaultConfigurable(self,typeAndName,requester):
211  """Return the configurable instance corresponding to the toolhandle if possible.
212  Otherwise return None"""
213  global log
214  # find the module
215  typeAndNameTuple = typeAndName.split('/')
216  confType = typeAndNameTuple[0]
217  confClass=ConfigurableDb.getConfigurable(confType)
218  # check the type of the configurable
219  if not derives_from(confClass,self._confTypeName):
220  log.error( "%s: Configurable %s is not a %s",
221  requester, confType, self._confTypeName )
222  return None
223  try:
224  confName = typeAndNameTuple[1]
225  except IndexError:
226  return confClass() # use default name
227  else:
228  return confClass(confName)
229 
230 
231  def convertDefaultToBeSet( self, obj, default ):
232  # turn string into handle
233  isString = type(default) == str
234  if not isString and self.isConfig(default):
235 # print self.fullPropertyName(obj) + ": Setting default configurable: %r" % default
236  return default
237  elif isString or self.isHandle(default):
238  if isString:
239  # convert string into handle
240  typeAndName = default
241  default = self._handleType( typeAndName )
242  else:
243  typeAndName = default.typeAndName
244  if not self._handleType.isPublic:
245  if not typeAndName:
246  return None
247  # Find corresponding default configurable of private handles
248  try:
249  conf = self.getDefaultConfigurable(typeAndName, self.fullPropertyName(obj))
250 # print self.fullPropertyName(obj) + ": Setting default private configurable (from default handle): %r" % conf
251  except AttributeError,e:
252  # change type of exception to avoid false error message
253  raise RuntimeError(*e.args)
254  if conf is None:
255  raise RuntimeError( "%s: Default configurable for class %s not found in ConfigurableDb.CfgDb" % \
256  (self.fullPropertyName(obj),default.getType() ) )
257  return conf
258  else: # not a config, not a handle, not a string
259  raise TypeError( "%s: default value %r is not of type %s or %s" % \
260  (self.fullPropertyName(obj),default,self._confTypeName,self._handleType.__name__) )
261 
262  return default
263 
264  def convertValueToBeSet( self, obj, value ):
265  if value is None: value = ''
266  isString = type(value) == str
267  if isString:
268  # create an new handle
269  return self._handleType(value)
270  elif self.isHandle(value):
271  # make a copy of the handle
272  return self._handleType(value.toStringProperty())
273  elif self.isConfig(value):
274  if self._handleType.isPublic:
275  # A public tool must be registered to ToolSvc before assigning it
276  if derives_from(value,'ConfigurableAlgTool'):
277  if not value.isInToolSvc():
278  suggestion = 'You may need to add jobOptions lines something like:' + os.linesep + \
279  'from AthenaCommon.AppMgr import ToolSvc' + os.linesep + \
280  'ToolSvc += '
281  if value.getName() == value.getType(): # using default name
282  suggestion += '%s()' % value.__class__.__name__
283  else: # using user-defined name
284  suggestion += '%s(%r)' % (value.__class__.__name__,value.getName())
285  raise RuntimeError( self.fullPropertyName(obj) +
286  ': Public tool %s is not yet in ToolSvc. %s' %
287  (value.getJobOptName(),suggestion) )
288  # make it read-only
289  return self._handleType(value.toStringProperty())
290  elif value.hasParent( obj.getJobOptName() ):
291  # is already a child, keep as-is
292  return value
293  else:
294  # make a copy of the configurable
295  value = obj.copyChildAndSetParent( value, obj.getJobOptName() )
296  # ensure that the new object is in allConfigurables
297  obj.allConfigurables[value.name()] = value
298  return value
299  else:
300  raise TypeError( "Property %s value %r is not a %s nor a %s nor a string" % \
301  (self.fullPropertyName(obj),value,self._confTypeName,self._handleType.__name__) )
302 
303  return value
304 
305 
307  def __init__( self, descr, docString, default ):
308  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle )
309 
310 
312  def __init__( self, descr, docString, default ):
313  """<descr>: the real property in the object instance (from __slots__)
314  <confTypeName>: string indicating the (base) class of allowed Configurables to be assigned.
315  <handleType>: real python handle type (e.g. PublicToolHandle, PrivateToolHandle, ...)
316  """
317  GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default).handleType, GaudiHandleArray )
318  self.arrayType = type(default)
319 
320 
321  def checkType( self, obj, value ):
322  if not isinstance( value, list ) and not isinstance( value, self.arrayType ):
323  raise TypeError( "%s: Value %r is not a list nor a %s" % \
324  ( self.fullPropertyName(obj), value, self.arrayType.__name__ ) )
325 
326 
327  def convertDefaultToBeSet( self, obj, default ):
328  self.checkType( obj, default )
329  newDefault = self.arrayType()
330  for d in default:
331  cd = GaudiHandlePropertyProxyBase.convertDefaultToBeSet( self, obj, d )
332  if cd: newDefault.append( cd )
333 
334  return newDefault
335 
336 
337  def convertValueToBeSet( self, obj, value ):
338  self.checkType( obj, value )
339  newValue = self.arrayType()
340  for v in value:
341  cv = GaudiHandlePropertyProxyBase.convertValueToBeSet( self, obj, v )
342  if cv: newValue.append( cv )
343 
344  return newValue
345 
346 
347 
348 def PropertyProxyFactory( descr, doc, default ):
349 # print "PropertyProxyFactory( %s, %r )" % (descr.__name__,default)
350  if isinstance(default,GaudiHandleArray):
351  return GaudiHandleArrayPropertyProxy( descr, doc, default )
352 
353  if isinstance(default,GaudiHandle):
354  return GaudiHandlePropertyProxy( descr, doc, default )
355 
356  return PropertyProxy( descr, doc, default )
string type
Definition: gaudirun.py:126