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, runtime=None):
165 props = IProperties(self) 166 if runtime is None: 167 runtime = self.set_runtime_properties 168 props.setProperty(propname, value, source, runtime=runtime)
169
170 - def getProperties(self):
171 return IProperties(self)
172
173 - def render(self, value):
174 props = IProperties(self) 175 return props.render(value)
176 177 178
179 -class PropertyMap:
180 """ 181 Privately-used mapping object to implement WithProperties' substitutions, 182 including the rendering of None as ''. 183 """ 184 colon_minus_re = re.compile(r"(.*):-(.*)") 185 colon_tilde_re = re.compile(r"(.*):~(.*)") 186 colon_plus_re = re.compile(r"(.*):\+(.*)")
187 - def __init__(self, properties):
188 # use weakref here to avoid a reference loop 189 self.properties = weakref.ref(properties) 190 self.temp_vals = {}
191
192 - def __getitem__(self, key):
193 properties = self.properties() 194 assert properties is not None 195 196 def colon_minus(mo): 197 # %(prop:-repl)s 198 # if prop exists, use it; otherwise, use repl 199 prop, repl = mo.group(1,2) 200 if prop in self.temp_vals: 201 return self.temp_vals[prop] 202 elif properties.has_key(prop): 203 return properties[prop] 204 else: 205 return repl
206 207 def colon_tilde(mo): 208 # %(prop:~repl)s 209 # if prop exists and is true (nonempty), use it; otherwise, use repl 210 prop, repl = mo.group(1,2) 211 if prop in self.temp_vals and self.temp_vals[prop]: 212 return self.temp_vals[prop] 213 elif properties.has_key(prop) and properties[prop]: 214 return properties[prop] 215 else: 216 return repl
217 218 def colon_plus(mo): 219 # %(prop:+repl)s 220 # if prop exists, use repl; otherwise, an empty string 221 prop, repl = mo.group(1,2) 222 if properties.has_key(prop) or prop in self.temp_vals: 223 return repl 224 else: 225 return '' 226 227 for regexp, fn in [ 228 ( self.colon_minus_re, colon_minus ), 229 ( self.colon_tilde_re, colon_tilde ), 230 ( self.colon_plus_re, colon_plus ), 231 ]: 232 mo = regexp.match(key) 233 if mo: 234 rv = fn(mo) 235 break 236 else: 237 # If explicitly passed as a kwarg, use that, 238 # otherwise, use the property value. 239 if key in self.temp_vals: 240 rv = self.temp_vals[key] 241 else: 242 rv = properties[key] 243 244 # translate 'None' to an empty string 245 if rv is None: rv = '' 246 return rv 247
248 - def add_temporary_value(self, key, val):
249 'Add a temporary value (to support keyword arguments to WithProperties)' 250 self.temp_vals[key] = val
251
252 - def clear_temporary_values(self):
253 self.temp_vals = {}
254
255 -class WithProperties(util.ComparableMixin):
256 """ 257 This is a marker class, used fairly widely to indicate that we 258 want to interpolate build properties. 259 """ 260 261 implements(IRenderable) 262 compare_attrs = ('fmtstring', 'args') 263
264 - def __init__(self, fmtstring, *args, **lambda_subs):
265 self.fmtstring = fmtstring 266 self.args = args 267 if not self.args: 268 self.lambda_subs = lambda_subs 269 for key, val in self.lambda_subs.iteritems(): 270 if not callable(val): 271 raise ValueError('Value for lambda substitution "%s" must be callable.' % key) 272 elif lambda_subs: 273 raise ValueError('WithProperties takes either positional or keyword substitutions, not both.')
274
275 - def getRenderingFor(self, build):
276 pmap = build.getProperties().pmap 277 if self.args: 278 strings = [] 279 for name in self.args: 280 strings.append(pmap[name]) 281 s = self.fmtstring % tuple(strings) 282 else: 283 for k,v in self.lambda_subs.iteritems(): 284 pmap.add_temporary_value(k, v(build)) 285 s = self.fmtstring % pmap 286 pmap.clear_temporary_values() 287 return s
288 289
290 -class Property(util.ComparableMixin):
291 """ 292 An instance of this class renders a property of a build. 293 """ 294 295 implements(IRenderable) 296 297 compare_attrs = ('key','default', 'defaultWhenFalse') 298
299 - def __init__(self, key, default=None, defaultWhenFalse=True):
300 """ 301 @param key: Property to render. 302 @param default: Value to use if property isn't set. 303 @param defaultWhenFalse: When true (default), use default value 304 if property evaluates to False. Otherwise, use default value 305 only when property isn't set. 306 """ 307 self.key = key 308 self.default = default 309 self.defaultWhenFalse = defaultWhenFalse
310
311 - def getRenderingFor(self, build):
312 if self.defaultWhenFalse: 313 return build.getProperty(self.key) or self.default 314 else: 315 return build.getProperty(self.key, default=self.default)
316 317
318 -class _DefaultRenderer:
319 """ 320 Default IRenderable adaptor. Calls .getRenderingFor if availble, otherwise 321 returns argument unchanged. 322 """ 323 324 implements(IRenderable) 325
326 - def __init__(self, value):
327 try: 328 self.renderer = value.getRenderingFor 329 except AttributeError: 330 self.renderer = lambda _: value
331
332 - def getRenderingFor(self, build):
333 return self.renderer(build)
334 335 registerAdapter(_DefaultRenderer, object, IRenderable) 336 337
338 -class _ListRenderer:
339 """ 340 List IRenderable adaptor. Maps Build.render over the list. 341 """ 342 343 implements(IRenderable) 344
345 - def __init__(self, value):
346 self.value = value
347
348 - def getRenderingFor(self, build):
349 return [ build.render(e) for e in self.value ]
350 351 registerAdapter(_ListRenderer, list, IRenderable) 352 353
354 -class _TupleRenderer:
355 """ 356 Tuple IRenderable adaptor. Maps Build.render over the tuple. 357 """ 358 359 implements(IRenderable) 360
361 - def __init__(self, value):
362 self.value = value
363
364 - def getRenderingFor(self, build):
365 return tuple([ build.render(e) for e in self.value ])
366 367 registerAdapter(_TupleRenderer, tuple, IRenderable) 368 369
370 -class _DictRenderer:
371 """ 372 Dict IRenderable adaptor. Maps Build.render over the keya and values in the dict. 373 """ 374 375 implements(IRenderable) 376
377 - def __init__(self, value):
378 self.value = value
379
380 - def getRenderingFor(self, build):
381 return dict([ (build.render(k), build.render(v)) for k,v in self.value.iteritems() ])
382 383 384 registerAdapter(_DictRenderer, dict, IRenderable) 385