Package buildbot :: Package process :: Module properties
[frames] | no frames]

Source Code for Module buildbot.process.properties

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import re 
 17  import weakref 
 18  from buildbot import util 
 19  from buildbot.interfaces import IRenderable, IProperties 
 20  from twisted.python.components import registerAdapter 
 21  from zope.interface import implements 
 22   
23 -class Properties(util.ComparableMixin):
24 """ 25 I represent a set of properties that can be interpolated into various 26 strings in buildsteps. 27 28 @ivar properties: dictionary mapping property values to tuples 29 (value, source), where source is a string identifing the source 30 of the property. 31 32 Objects of this class can be read like a dictionary -- in this case, 33 only the property value is returned. 34 35 As a special case, a property value of None is returned as an empty 36 string when used as a mapping. 37 """ 38 39 compare_attrs = ('properties',) 40 implements(IProperties) 41
42 - def __init__(self, **kwargs):
43 """ 44 @param kwargs: initial property values (for testing) 45 """ 46 self.properties = {} 47 # Track keys which are 'runtime', and should not be 48 # persisted if a build is rebuilt 49 self.runtime = set() 50 self.pmap = PropertyMap(self) 51 self.build = None # will be set by the Build when starting 52 if kwargs: self.update(kwargs, "TEST")
53
54 - def __getstate__(self):
55 d = self.__dict__.copy() 56 del d['pmap'] 57 d['build'] = None 58 return d
59
60 - def __setstate__(self, d):
61 self.__dict__ = d 62 self.pmap = PropertyMap(self) 63 if not hasattr(self, 'runtime'): 64 self.runtime = set()
65
66 - def __contains__(self, name):
67 return name in self.properties
68
69 - def __getitem__(self, name):
70 """Just get the value for this property.""" 71 rv = self.properties[name][0] 72 return rv
73
74 - def __nonzero__(self):
75 return not not self.properties
76
77 - def getPropertySource(self, name):
78 return self.properties[name][1]
79
80 - def asList(self):
81 """Return the properties as a sorted list of (name, value, source)""" 82 l = [ (k, v[0], v[1]) for k,v in self.properties.iteritems() ] 83 l.sort() 84 return l
85
86 - def asDict(self):
87 """Return the properties as a simple key:value dictionary""" 88 return dict(self.properties)
89
90 - def __repr__(self):
91 return ('Properties(**' + 92 repr(dict((k,v[0]) for k,v in self.properties.iteritems())) + 93 ')')
94
95 - def update(self, dict, source, runtime=False):
96 """Update this object from a dictionary, with an explicit source specified.""" 97 for k, v in dict.items(): 98 self.properties[k] = (v, source) 99 if runtime: 100 self.runtime.add(k)
101
102 - def updateFromProperties(self, other):
103 """Update this object based on another object; the other object's """ 104 self.properties.update(other.properties) 105 self.runtime.update(other.runtime)
106
107 - def updateFromPropertiesNoRuntime(self, other):
108 """Update this object based on another object, but don't 109 include properties that were marked as runtime.""" 110 for k,v in other.properties.iteritems(): 111 if k not in other.runtime: 112 self.properties[k] = v
113 114 # IProperties methods 115
116 - def getProperty(self, name, default=None):
117 return self.properties.get(name, (default,))[0]
118
119 - def hasProperty(self, name):
120 return self.properties.has_key(name)
121 122 has_key = hasProperty 123
124 - def setProperty(self, name, value, source, runtime=False):
125 self.properties[name] = (value, source) 126 if runtime: 127 self.runtime.add(name)
128
129 - def getProperties(self):
130 return self
131
132 - def getBuild(self):
133 return self.build
134
135 - def render(self, value):
136 renderable = IRenderable(value) 137 return renderable.getRenderingFor(self)
138 139
140 -class PropertiesMixin:
141 """ 142 A mixin to add L{IProperties} methods to a class which does not implement 143 the interface, but which can be coerced to the interface via an adapter. 144 145 This is useful because L{IProperties} methods are often called on L{Build} 146 and L{BuildStatus} objects without first coercing them. 147 148 @ivar set_runtime_properties: the default value for the C{runtime} 149 parameter of L{setProperty}. 150 """ 151 152 set_runtime_properties = False 153
154 - def getProperty(self, propname, default=None):
155 props = IProperties(self) 156 return props.getProperty(propname, default)
157
158 - def hasProperty(self, propname):
159 props = IProperties(self) 160 return props.hasProperty(propname)
161 162 has_key = hasProperty 163
164 - def setProperty(self, propname, value, source='Unknown', runtime=None):
165 # source is not optional in IProperties, but is optional here to avoid 166 # breaking user-supplied code that fails to specify a source 167 props = IProperties(self) 168 if runtime is None: 169 runtime = self.set_runtime_properties 170 props.setProperty(propname, value, source, runtime=runtime)
171
172 - def getProperties(self):
173 return IProperties(self)
174
175 - def render(self, value):
176 props = IProperties(self) 177 return props.render(value)
178 179 180
181 -class PropertyMap:
182 """ 183 Privately-used mapping object to implement WithProperties' substitutions, 184 including the rendering of None as ''. 185 """ 186 colon_minus_re = re.compile(r"(.*):-(.*)") 187 colon_tilde_re = re.compile(r"(.*):~(.*)") 188 colon_plus_re = re.compile(r"(.*):\+(.*)")
189 - def __init__(self, properties):
190 # use weakref here to avoid a reference loop 191 self.properties = weakref.ref(properties) 192 self.temp_vals = {}
193
194 - def __getitem__(self, key):
195 properties = self.properties() 196 assert properties is not None 197 198 def colon_minus(mo): 199 # %(prop:-repl)s 200 # if prop exists, use it; otherwise, use repl 201 prop, repl = mo.group(1,2) 202 if prop in self.temp_vals: 203 return self.temp_vals[prop] 204 elif properties.has_key(prop): 205 return properties[prop] 206 else: 207 return repl
208 209 def colon_tilde(mo): 210 # %(prop:~repl)s 211 # if prop exists and is true (nonempty), use it; otherwise, use repl 212 prop, repl = mo.group(1,2) 213 if prop in self.temp_vals and self.temp_vals[prop]: 214 return self.temp_vals[prop] 215 elif properties.has_key(prop) and properties[prop]: 216 return properties[prop] 217 else: 218 return repl
219 220 def colon_plus(mo): 221 # %(prop:+repl)s 222 # if prop exists, use repl; otherwise, an empty string 223 prop, repl = mo.group(1,2) 224 if properties.has_key(prop) or prop in self.temp_vals: 225 return repl 226 else: 227 return '' 228 229 for regexp, fn in [ 230 ( self.colon_minus_re, colon_minus ), 231 ( self.colon_tilde_re, colon_tilde ), 232 ( self.colon_plus_re, colon_plus ), 233 ]: 234 mo = regexp.match(key) 235 if mo: 236 rv = fn(mo) 237 break 238 else: 239 # If explicitly passed as a kwarg, use that, 240 # otherwise, use the property value. 241 if key in self.temp_vals: 242 rv = self.temp_vals[key] 243 else: 244 rv = properties[key] 245 246 # translate 'None' to an empty string 247 if rv is None: rv = '' 248 return rv 249
250 - def add_temporary_value(self, key, val):
251 'Add a temporary value (to support keyword arguments to WithProperties)' 252 self.temp_vals[key] = val
253
254 - def clear_temporary_values(self):
255 self.temp_vals = {}
256
257 -class WithProperties(util.ComparableMixin):
258 """ 259 This is a marker class, used fairly widely to indicate that we 260 want to interpolate build properties. 261 """ 262 263 implements(IRenderable) 264 compare_attrs = ('fmtstring', 'args') 265
266 - def __init__(self, fmtstring, *args, **lambda_subs):
267 self.fmtstring = fmtstring 268 self.args = args 269 if not self.args: 270 self.lambda_subs = lambda_subs 271 for key, val in self.lambda_subs.iteritems(): 272 if not callable(val): 273 raise ValueError('Value for lambda substitution "%s" must be callable.' % key) 274 elif lambda_subs: 275 raise ValueError('WithProperties takes either positional or keyword substitutions, not both.')
276
277 - def getRenderingFor(self, build):
278 pmap = build.getProperties().pmap 279 if self.args: 280 strings = [] 281 for name in self.args: 282 strings.append(pmap[name]) 283 s = self.fmtstring % tuple(strings) 284 else: 285 for k,v in self.lambda_subs.iteritems(): 286 pmap.add_temporary_value(k, v(build)) 287 s = self.fmtstring % pmap 288 pmap.clear_temporary_values() 289 return s
290 291
292 -class Property(util.ComparableMixin):
293 """ 294 An instance of this class renders a property of a build. 295 """ 296 297 implements(IRenderable) 298 299 compare_attrs = ('key','default', 'defaultWhenFalse') 300
301 - def __init__(self, key, default=None, defaultWhenFalse=True):
302 """ 303 @param key: Property to render. 304 @param default: Value to use if property isn't set. 305 @param defaultWhenFalse: When true (default), use default value 306 if property evaluates to False. Otherwise, use default value 307 only when property isn't set. 308 """ 309 self.key = key 310 self.default = default 311 self.defaultWhenFalse = defaultWhenFalse
312
313 - def getRenderingFor(self, props):
314 if self.defaultWhenFalse: 315 rv = props.getProperty(self.key) 316 if rv: 317 return rv 318 else: 319 if props.hasProperty(self.key): 320 return props.getProperty(self.key) 321 322 return props.render(self.default)
323
324 -class _DefaultRenderer(object):
325 """ 326 Default IRenderable adaptor. Calls .getRenderingFor if availble, otherwise 327 returns argument unchanged. 328 """ 329 330 implements(IRenderable) 331
332 - def __init__(self, value):
333 try: 334 self.renderer = value.getRenderingFor 335 except AttributeError: 336 self.renderer = lambda _: value
337
338 - def getRenderingFor(self, build):
339 return self.renderer(build)
340 341 registerAdapter(_DefaultRenderer, object, IRenderable) 342 343
344 -class _ListRenderer(object):
345 """ 346 List IRenderable adaptor. Maps Build.render over the list. 347 """ 348 349 implements(IRenderable) 350
351 - def __init__(self, value):
352 self.value = value
353
354 - def getRenderingFor(self, build):
355 return [ build.render(e) for e in self.value ]
356 357 registerAdapter(_ListRenderer, list, IRenderable) 358 359
360 -class _TupleRenderer(object):
361 """ 362 Tuple IRenderable adaptor. Maps Build.render over the tuple. 363 """ 364 365 implements(IRenderable) 366
367 - def __init__(self, value):
368 self.value = value
369
370 - def getRenderingFor(self, build):
371 return tuple([ build.render(e) for e in self.value ])
372 373 registerAdapter(_TupleRenderer, tuple, IRenderable) 374 375
376 -class _DictRenderer(object):
377 """ 378 Dict IRenderable adaptor. Maps Build.render over the keya and values in the dict. 379 """ 380 381 implements(IRenderable) 382
383 - def __init__(self, value):
384 self.value = value
385
386 - def getRenderingFor(self, build):
387 return dict([ (build.render(k), build.render(v)) for k,v in self.value.iteritems() ])
388 389 390 registerAdapter(_DictRenderer, dict, IRenderable) 391