Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(340)

Side by Side Diff: scripts/slave/recipe_configs_util.py

Issue 23889036: Refactor the way that TestApi works so that it is actually useful. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: License headers Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « scripts/slave/recipe_config.py ('k') | scripts/slave/recipe_loader.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Recipe Configuration Meta DSL.
6
7 This module contains, essentially, a DSL for writing composable configurations.
8 You start by defining a schema which describes how your configuration blobs will
9 be structured, and what data they can contain. For example:
10
11 FakeSchema = lambda main_val=True, mode='Happy': ConfigGroup(
12 config_group = ConfigGroup(
13 item_a = SimpleConfig(int),
14 item_b = DictConfig(),
15 ),
16 extra_setting = SetConfig(str),
17
18 MAIN_DETERMINANT = StaticConfig(main_val),
19 CONFIG_MODE = StaticConfig(mode),
20 )
21
22 In short, a 'schema' is a callable which can take zero arguments (it can contain
23 default arguments as well, for setting defaults, tweaking the schema, etc.), and
24 returning a ConfigGroup.
25
26 Every type used in the schema derives from ConfigBase. It has the general
27 characteristics that it's a fixed-type container. It tends to impersonate the
28 data type that it stores (so you can manipulate the config objects like normal
29 python data), but also provides type checking and type conversion assistence
30 (so you can easily render your configurations to JSON).
31
32 Once you have your schema, you define some testing data:
33 TEST_MAP = {
34 'MAIN_DETERMINANT': (True, False),
35 'CONFIG_MODE': ('Happy', 'Sad'),
36 }
37 TEST_NAME_FORMAT = '%(MAIN_DETERMINANT)s-%(CONFIG_MODE)s'
38
39 The test map tells the test harness what parameters it should instantiate the
40 schema with, and what values those parameters should take. The test harness will
41 generate all possible permutations of input parameters, and will save them to
42 disk.
43
44 The test format is a string format (or a function taking a dictionary of
45 variable assignments) which will be used to name the test files
46 and test cases for this configuration.
47
48 Once you have all that, you can create a configuration context:
49
50 config_ctx = config_item_context(FakeSchema, TEST_MAP, TEST_NAME_FORMAT)
51
52 config_ctx is a python decorator which you can use to create composable
53 configuration functions. For example:
54
55 @config_ctx()
56 def cool(c):
57 if c.CONFIG_MODE == 'Happy':
58 c.config_group.item_a = 100
59 else:
60 c.config_group.item_a = -100
61
62 @config_ctx()
63 def gnarly(c):
64 c.extra_setting = 'gnarly!'
65
66 @config_ctx(includes=('cool', 'gnarly'))
67 def combo(c):
68 if c.MAIN_DETERMINANT:
69 c.config_group.item_b['nickname'] = 'purple'
70 c.extra_setting += ' cows!'
71 else:
72 c.config_group.item_b['nickname'] = 'sad times'
73
74 If I now call:
75
76 combo()
77
78 I will get back a configuraton object whose schema is FakeSchema, and whose
79 data is the accumulation of cool(), gnarly(), and combo(). I can continue to
80 manipulate this configuraton object, use its data, or render it to json.
81
82 Using this system should allow you to create rich, composible,
83 modular configurations. See the documentation on config_item_context and the
84 BaseConfig derivatives for more info.
85 """
86
87 import collections
88 import functools
89 import types
90
91 class BadConf(Exception):
92 pass
93
94 def config_item_context(CONFIG_SCHEMA, VAR_TEST_MAP, TEST_NAME_FORMAT,
95 TEST_FILE_FORMAT=None):
96 """Create a configuration context.
97
98 Args:
99 CONFIG_SCHEMA: This is a function which can take a minimum of zero arguments
100 and returns an instance of BaseConfig. This BaseConfig
101 defines the schema for all configuration objects manipulated
102 in this context.
103 VAR_TEST_MAP: A dict mapping arg_name to an iterable of values. This
104 provides the test harness with sufficient information to
105 generate all possible permutations of inputs for the
106 CONFIG_SCHEMA function.
107 TEST_NAME_FORMAT: A string format (or function) for naming tests and test
108 expectation files. It will be formatted/called with a
109 dictionary of arg_name to value (using arg_names and
110 values generated from VAR_TEST_MAP)
111 TEST_FILE_FORMAT: Similar to TEST_NAME_FORMAT, but for test files. Defaults
112 to TEST_NAME_FORMAT.
113
114 Returns a config_ctx decorator for this context.
115 """
116
117 def config_ctx(group=None, includes=None, deps=None, no_test=False,
118 is_root=False, config_vars=None):
119 """
120 A decorator for functions which modify a given schema of configs.
121 Examples continue using the schema and config_items defined in the module
122 docstring.
123
124 This decorator provides a series of related functions:
125 * Any function decorated with this will be registered into this config
126 context by __name__. This enables some of the other following features
127 to work.
128 * Alters the signature of the function so that it can recieve an extra
129 parameter 'final'. See the documentation for final on inner().
130 * Provides various convenience and error checking facilities.
131 * In particular, this decorator will prevent you from calling the same
132 config_ctx on a given config blob more than once (with the exception
133 of setting final=False. See inner())
134
135 Args:
136 group(str) - Using this decorator with the `group' argument causes the
137 decorated function to be a member of that group. Members of a group are
138 mutually exclusive on the same configuration blob. For example, only
139 one of these two functions could be applied to the config blob c:
140 @config_ctx(group='a')
141 def bob(c):
142 c.extra_setting = "bob mode"
143
144 @config_ctx(group='a')
145 def bill(c):
146 c.extra_setting = "bill mode"
147
148 includes(iterable(str)) - Any config items named in the includes list will
149 be run against the config blob before the decorated function can modify
150 it. If an inclusion is already applied to the config blob, it's skipped
151 without applying/raising BadConf. Example:
152 @config_ctx(includes=('bob', 'cool'))
153 def charlie(c):
154 c.config_group.item_b = 25
155 The result of this config_ctx (assuming default values for the schema)
156 would be:
157 {'config_group': { 'item_a': 100, 'item_b': 25 },
158 'extra_setting': 'gnarly!'}
159
160 deps(iterable(str)) - One or more groups which must be satisfied before
161 this config_ctx can be applied to a config_blob. If you invoke
162 a config_ctx on a blob without having all of its deps satisfied,
163 you'll get a BadConf exception.
164
165 no_test(bool) - If set to True, then this config_ctx will be skipped by
166 the test harness. This defaults to (False or bool(deps)), since
167 config_items with deps will never be satisfiable as the first
168 config_ctx applied to a blob.
169
170 is_root(bool) - If set to True on an item, this item will become the
171 'basis' item for all other configurations in this group. That means that
172 it will be implicitly included in all other config_items. There may only
173 ever be one root item.
174
175 Additionally, the test harness uses the root item to probe for invalid
176 configuration combinations by running the root item first (if there is
177 one), and skipping the configuration combination if the root config
178 item throws BadConf.
179
180 config_vars(dict) - A dictionary mapping of { CONFIG_VAR: <value> }. This
181 sets the input contidions for the CONFIG_SCHEMA.
182
183 Returns a new decorated version of this function (see inner()).
184 """
185 def decorator(f):
186 name = f.__name__
187 @functools.wraps(f)
188 def inner(config=None, final=True, optional=False, **kwargs):
189 """This is the function which is returned from the config_ctx
190 decorator.
191
192 It applies all of the logic mentioned in the config_ctx docstring
193 above, and alters the function signature slightly.
194
195 Args:
196 config - The config blob that we intend to manipulate. This is passed
197 through to the function after checking deps and including includes.
198 After the function manipulates it, it is automatically returned.
199
200 final(bool) - Set to True by default, this will record the application
201 of this config_ctx to `config', which will prevent the config_ctx
202 from being applied to `config' again. It also is used to see if the
203 config blob satisfies deps for subsequent config_ctx applications
204 (i.e. in order for a config_ctx to satisfy a dependency, it must
205 be applied with final=True).
206
207 This is useful to apply default values while allowing the config to
208 later override those values.
209
210 However, it's best if each config_ctx is final, because then you
211 can implement the config items with less error checking, since you
212 know that the item may only be applied once. For example, if your
213 item appends something to a list, but is called with final=False,
214 you'll have to make sure that you don't append the item twice, etc.
215
216 **kwargs - Passed through to the decorated function without harm.
217
218 Returns config and ignores the return value of the decorated function.
219 """
220 if config is None:
221 config = config_ctx.CONFIG_SCHEMA()
222 assert isinstance(config, ConfigGroup)
223 inclusions = config._inclusions # pylint: disable=W0212
224
225 # inner.IS_ROOT will be True or False at the time of invocation.
226 if (config_ctx.ROOT_CONFIG_ITEM and not inner.IS_ROOT and
227 config_ctx.ROOT_CONFIG_ITEM.__name__ not in inclusions):
228 config_ctx.ROOT_CONFIG_ITEM(config)
229
230 if name in inclusions:
231 if optional:
232 return config
233 raise BadConf('config_ctx "%s" is already in this config "%s"' %
234 (name, config.as_jsonish(include_hidden=True)))
235 if final:
236 inclusions.add(name)
237
238 for include in (includes or []):
239 if include in inclusions:
240 continue
241 try:
242 config_ctx.CONFIG_ITEMS[include](config)
243 except BadConf, e:
244 raise BadConf('config "%s" includes "%s", but [%s]' %
245 (name, include, e))
246
247 # deps are a list of group names. All groups must be represented
248 # in config already.
249 for dep_group in (deps or []):
250 if not (inclusions & config_ctx.MUTEX_GROUPS[dep_group]):
251 raise BadConf('dep group "%s" is unfulfilled for "%s"' %
252 (dep_group, name))
253
254 if group:
255 overlap = inclusions & config_ctx.MUTEX_GROUPS[group]
256 overlap.discard(name)
257 if overlap:
258 raise BadConf('"%s" is a member of group "%s", but %s already ran' %
259 (name, group, tuple(overlap)))
260
261 ret = f(config, **kwargs)
262 assert ret is None, 'Got return value (%s) from "%s"?' % (ret, name)
263
264 return config
265
266 def default_config_vars():
267 ret = {}
268 for include in (includes or []):
269 item = config_ctx.CONFIG_ITEMS[include]
270 ret.update(item.DEFAULT_CONFIG_VARS())
271 if config_vars:
272 ret.update(config_vars)
273 return ret
274 inner.DEFAULT_CONFIG_VARS = default_config_vars
275
276 assert name not in config_ctx.CONFIG_ITEMS
277 config_ctx.CONFIG_ITEMS[name] = inner
278 if group:
279 config_ctx.MUTEX_GROUPS.setdefault(group, set()).add(name)
280 inner.IS_ROOT = is_root
281 if is_root:
282 assert not config_ctx.ROOT_CONFIG_ITEM, (
283 'may only have one root config_ctx!')
284 config_ctx.ROOT_CONFIG_ITEM = inner
285 inner.IS_ROOT = True
286 inner.NO_TEST = no_test or bool(deps)
287 return inner
288 return decorator
289
290 # Internal state and testing data
291 config_ctx.I_AM_A_CONFIG_CTX = True
292 config_ctx.CONFIG_ITEMS = {}
293 config_ctx.MUTEX_GROUPS = {}
294 config_ctx.CONFIG_SCHEMA = CONFIG_SCHEMA
295 config_ctx.ROOT_CONFIG_ITEM = None
296 config_ctx.VAR_TEST_MAP = VAR_TEST_MAP
297
298 def formatter(obj, ext=None):
299 '''Converts format obj to a function taking var assignments.
300
301 Args:
302 obj (str or fn(assignments)): If obj is a str, it will be % formatted
303 with assignments (which is a dict of variables from VAR_TEST_MAP).
304 Otherwise obj will be invoked with assignments, and expected to return
305 a fully-rendered string.
306 ext (None or str): Optionally specify an extension to enforce on the
307 format. This enforcement occurs after obj is finalized to a string. If
308 the string doesn't end with ext, it will be appended.
309 '''
310 def inner(var_assignments):
311 ret = ''
312 if isinstance(obj, basestring):
313 ret = obj % var_assignments
314 else:
315 ret = obj(var_assignments)
316 if ext and not ret.endswith(ext):
317 ret += ext
318 return ret
319 return inner
320 config_ctx.TEST_NAME_FORMAT = formatter(TEST_NAME_FORMAT)
321 config_ctx.TEST_FILE_FORMAT = formatter(
322 (TEST_FILE_FORMAT or TEST_NAME_FORMAT), ext='.json')
323 return config_ctx
324
325
326 class AutoHide(object):
327 pass
328 AutoHide = AutoHide()
329
330 class ConfigBase(object):
331 """This is the root interface for all config schema types."""
332
333 def __init__(self, hidden=AutoHide):
334 """
335 Args:
336 hidden -
337 True: This object will be excluded from printing when the config blob
338 is rendered with ConfigGroup.as_jsonish(). You still have full
339 read/write access to this blob otherwise though.
340 False: This will be printed as part of ConfigGroup.as_jsonish()
341 AutoHide: This will be printed as part of ConfigGroup.as_jsonish() only
342 if self._is_default() is false.
343 """
344 # work around subclasses which override __setattr__
345 object.__setattr__(self, '_hidden_mode', hidden)
346 object.__setattr__(self, '_inclusions', set())
347
348 def get_val(self):
349 """Gets the native value of this config object."""
350 return self
351
352 def set_val(self, val):
353 """Resets the value of this config object using data in val."""
354 raise NotImplementedError
355
356 def reset(self):
357 """Resets the value of this config object to it's initial state."""
358 raise NotImplementedError
359
360 def as_jsonish(self, include_hidden=False):
361 """Returns the value of this config object as simple types."""
362 raise NotImplementedError
363
364 def complete(self):
365 """Returns True iff this configuraton blob is fully viable."""
366 raise NotImplementedError
367
368 def _is_default(self):
369 """Returns True iff this configuraton blob is the default value."""
370 raise NotImplementedError
371
372 @property
373 def _hidden(self):
374 """Returns True iff this configuraton blob is hidden."""
375 if self._hidden_mode is AutoHide:
376 return self._is_default()
377 return self._hidden_mode
378
379
380 class ConfigGroup(ConfigBase):
381 """Allows you to provide hierarchy to a configuration schema.
382
383 Example usage:
384 config_blob = ConfigGroup(
385 some_item = SimpleConfig(str),
386 group = ConfigGroup(
387 numbahs = SetConfig(int),
388 ),
389 )
390 config_blob.some_item = "hello"
391 config_blob.group.numbahs.update(range(10))
392 """
393
394 def __init__(self, hidden=AutoHide, **type_map):
395 """Expects type_map to be {python_name -> ConfigBase} instance."""
396 super(ConfigGroup, self).__init__(hidden)
397 assert type_map, 'A ConfigGroup with no type_map is meaningless.'
398
399 object.__setattr__(self, '_type_map', type_map)
400 for name, typeval in self._type_map.iteritems():
401 assert isinstance(typeval, ConfigBase)
402 object.__setattr__(self, name, typeval)
403
404 def __getattribute__(self, name):
405 obj = object.__getattribute__(self, name)
406 if isinstance(obj, ConfigBase):
407 return obj.get_val()
408 else:
409 return obj
410
411 def __setattr__(self, name, val):
412 obj = object.__getattribute__(self, name)
413 assert isinstance(obj, ConfigBase)
414 obj.set_val(val)
415
416 def __delattr__(self, name):
417 obj = object.__getattribute__(self, name)
418 assert isinstance(obj, ConfigBase)
419 obj.reset()
420
421 def set_val(self, val):
422 if isinstance(val, ConfigBase):
423 val = val.as_jsonish(include_hidden=True)
424 assert isinstance(val, dict)
425 for name, config_obj in self._type_map.iteritems():
426 if name in val:
427 config_obj.set_val(val.pop())
428 assert not val, "Got extra keys while setting ConfigGroup: %s" % val
429
430 def as_jsonish(self, include_hidden=False):
431 return dict(
432 (n, v.as_jsonish(include_hidden)) for n, v in self._type_map.iteritems()
433 if (include_hidden or not v._hidden)) # pylint: disable=W0212
434
435 def reset(self):
436 for v in self._type_map.values():
437 v.reset()
438
439 def complete(self):
440 return all(v.complete() for v in self._type_map.values())
441
442 def _is_default(self):
443 # pylint: disable=W0212
444 return all(v._is_default() for v in self._type_map.values())
445
446
447 class ConfigList(ConfigBase, collections.MutableSequence):
448 """Allows you to provide an ordered repetition to a configuration schema.
449
450 Example usage:
451 config_blob = ConfigGroup(
452 some_items = ConfigList(
453 ConfigGroup(
454 herp = SimpleConfig(int),
455 derp = SimpleConfig(str)
456 )
457 )
458 )
459 config_blob.some_items.append({'herp': 1})
460 config_blob.some_items[0].derp = 'bob'
461 """
462
463 def __init__(self, item_schema, hidden=AutoHide):
464 """
465 Args:
466 item_schema: The schema of each object. Should be a function which returns
467 an instance of ConfigGroup.
468 """
469 super(ConfigList, self).__init__(hidden=hidden)
470 assert isinstance(item_schema, types.FunctionType)
471 assert isinstance(item_schema(), ConfigGroup)
472 self.item_schema = item_schema
473 self.data = []
474
475 def __getitem__(self, index):
476 return self.data.__getitem__(index)
477
478 def __setitem__(self, index, value):
479 datum = self.item_schema()
480 datum.set_val(value)
481 return self.data.__setitem__(index, datum)
482
483 def __delitem__(self, index):
484 return self.data.__delitem__(index)
485
486 def __len__(self):
487 return len(self.data)
488
489 def insert(self, index, value):
490 datum = self.item_schema()
491 datum.set_val(value)
492 return self.data.insert(index, datum)
493
494 def add(self):
495 self.append({})
496 return self[-1]
497
498 def reset(self):
499 self.data = []
500
501 def complete(self):
502 return all(i.complete() for i in self.data)
503
504 def set_val(self, data):
505 if isinstance(data, ConfigList):
506 data = data.as_jsonish(include_hidden=True)
507 assert isinstance(data, list)
508 self.reset()
509 for item in data:
510 self.append(item)
511
512 def as_jsonish(self, include_hidden=False):
513 return [i.as_jsonish(include_hidden) for i in self.data
514 if (include_hidden or not i._hidden)] # pylint: disable=W0212
515
516 def _is_default(self):
517 # pylint: disable=W0212
518 return all(v._is_default() for v in self.data)
519
520
521 class Dict(ConfigBase, collections.MutableMapping):
522 """Provides a semi-homogenous dict()-like configuration object."""
523
524 def __init__(self, item_fn=lambda i: i, jsonish_fn=dict, value_type=None,
525 hidden=AutoHide):
526 """
527 Args:
528 item_fn - A function which renders (k, v) pairs to input items for
529 jsonish_fn. Defaults to the identity function.
530 jsonish_fn - A function which renders a list of outputs of item_fn to a
531 JSON-compatiple python datatype. Defaults to dict().
532 value_type - A type object used for constraining/validating the values
533 assigned to this dictionary.
534 hidden - See ConfigBase.
535 """
536 super(Dict, self).__init__(hidden)
537 self.value_type = value_type
538 self.item_fn = item_fn
539 self.jsonish_fn = jsonish_fn
540 self.data = {}
541
542 def __getitem__(self, k):
543 return self.data.__getitem__(k)
544
545 def __setitem__(self, k, v):
546 if self.value_type:
547 assert isinstance(v, self.value_type)
548 return self.data.__setitem__(k, v)
549
550 def __delitem__(self, k):
551 return self.data.__delitem__(k)
552
553 def __iter__(self):
554 return iter(self.data)
555
556 def __len__(self):
557 return len(self.data)
558
559 def set_val(self, val):
560 if isinstance(val, Dict):
561 val = val.data
562 assert isinstance(val, dict)
563 assert all(isinstance(v, self.value_type) for v in val.itervalues())
564 self.data = val
565
566 def as_jsonish(self, _include_hidden=None):
567 return self.jsonish_fn(map(
568 self.item_fn, sorted(self.data.iteritems(), key=lambda x: x[0])))
569
570 def reset(self):
571 self.data.clear()
572
573 def complete(self):
574 return True
575
576 def _is_default(self):
577 return not self.data
578
579
580 class List(ConfigBase, collections.MutableSequence):
581 """Provides a semi-homogenous list()-like configuration object."""
582
583 def __init__(self, inner_type, jsonish_fn=list, hidden=AutoHide):
584 """
585 Args:
586 inner_type - The type of data contained in this set, e.g. str, int, ...
587 Can also be a tuple of types to allow more than one type.
588 jsonish_fn - A function used to reduce the list() to a JSON-compatible
589 python datatype. Defaults to list().
590 hidden - See ConfigBase.
591 """
592 super(List, self).__init__(hidden)
593 self.inner_type = inner_type
594 self.jsonish_fn = jsonish_fn
595 self.data = []
596
597 def __getitem__(self, index):
598 return self.data[index]
599
600 def __setitem__(self, index, value):
601 assert isinstance(value, self.inner_type)
602 self.data[index] = value
603
604 def __delitem__(self, index):
605 del self.data
606
607 def __len__(self):
608 return len(self.data)
609
610 def __radd__(self, other):
611 if not isinstance(other, list):
612 other = list(other)
613 return other + self.data
614
615 def insert(self, index, value):
616 assert isinstance(value, self.inner_type)
617 self.data.insert(index, value)
618
619 def set_val(self, val):
620 assert all(isinstance(v, self.inner_type) for v in val)
621 self.data = list(val)
622
623 def as_jsonish(self, _include_hidden=None):
624 return self.jsonish_fn(self.data)
625
626 def reset(self):
627 self.data = []
628
629 def complete(self):
630 return True
631
632 def _is_default(self):
633 return not self.data
634
635
636 class Set(ConfigBase, collections.MutableSet):
637 """Provides a semi-homogenous set()-like configuration object."""
638
639 def __init__(self, inner_type, jsonish_fn=list, hidden=AutoHide):
640 """
641 Args:
642 inner_type - The type of data contained in this set, e.g. str, int, ...
643 Can also be a tuple of types to allow more than one type.
644 jsonish_fn - A function used to reduce the set() to a JSON-compatible
645 python datatype. Defaults to list().
646 hidden - See ConfigBase.
647 """
648 super(Set, self).__init__(hidden)
649 self.inner_type = inner_type
650 self.jsonish_fn = jsonish_fn
651 self.data = set()
652
653 def __contains__(self, val):
654 return val in self.data
655
656 def __iter__(self):
657 return iter(self.data)
658
659 def __len__(self):
660 return len(self.data)
661
662 def add(self, value):
663 assert isinstance(value, self.inner_type)
664 self.data.add(value)
665
666 def discard(self, value):
667 self.data.discard(value)
668
669 def set_val(self, val):
670 assert all(isinstance(v, self.inner_type) for v in val)
671 self.data = set(val)
672
673 def as_jsonish(self, _include_hidden=None):
674 return self.jsonish_fn(sorted(self.data))
675
676 def reset(self):
677 self.data = set()
678
679 def complete(self):
680 return True
681
682 def _is_default(self):
683 return not self.data
684
685
686 class Single(ConfigBase):
687 """Provides a configuration object which holds a single 'simple' type."""
688
689 def __init__(self, inner_type, jsonish_fn=lambda x: x, empty_val=None,
690 required=True, hidden=AutoHide):
691 """
692 Args:
693 inner_type - The type of data contained in this object, e.g. str, int, ...
694 Can also be a tuple of types to allow more than one type.
695 jsonish_fn - A function used to reduce the data to a JSON-compatible
696 python datatype. Default is the identity function.
697 emtpy_val - The value to use when initializing this object or when calling
698 reset().
699 required(bool) - True iff this config item is required to have a
700 non-empty_val in order for it to be considered complete().
701 hidden - See ConfigBase.
702 """
703 super(Single, self).__init__(hidden)
704 self.inner_type = inner_type
705 self.jsonish_fn = jsonish_fn
706 self.empty_val = empty_val
707 self.data = empty_val
708 self.required = required
709
710 def get_val(self):
711 return self.data
712
713 def set_val(self, val):
714 if isinstance(val, Single):
715 val = val.data
716 assert val is self.empty_val or isinstance(val, self.inner_type)
717 self.data = val
718
719 def as_jsonish(self, _include_hidden=None):
720 return self.jsonish_fn(self.data)
721
722 def reset(self):
723 self.data = self.empty_val
724
725 def complete(self):
726 return not self.required or self.data is not self.empty_val
727
728 def _is_default(self):
729 return self.data is self.empty_val
730
731
732 class Static(ConfigBase):
733 """Holds a single, hidden, immutible data object.
734
735 This is very useful for holding the 'input' configuration values (i.e. those
736 which are in your VAR_TEST_MAP).
737 """
738
739 def __init__(self, value, hidden=AutoHide):
740 super(Static, self).__init__(hidden=hidden)
741 # Attempt to hash the value, which will ensure that it's immutable all the
742 # way down :).
743 hash(value)
744 self.data = value
745
746 def get_val(self):
747 return self.data
748
749 def set_val(self, val):
750 assert False
751
752 def as_jsonish(self, _include_hidden=None):
753 return self.data
754
755 def reset(self):
756 assert False
757
758 def complete(self):
759 return True
760
761 def _is_default(self):
762 return True
OLDNEW
« no previous file with comments | « scripts/slave/recipe_config.py ('k') | scripts/slave/recipe_loader.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698