| # Gaudi Framework, version v25r0 |

Home |  | Generated: Mon Feb 17 2014

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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
151 class GaudiHandlePropertyProxyBase(PropertyProxy):
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
306 class GaudiHandlePropertyProxy(GaudiHandlePropertyProxyBase):
307 def __init__( self, descr, docString, default ):
308 GaudiHandlePropertyProxyBase.__init__( self, descr, docString, default, type(default), GaudiHandle )
309
310
311 class GaudiHandleArrayPropertyProxy(GaudiHandlePropertyProxyBase):
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 )