Index: third_party/recipe_engine/config.py |
diff --git a/third_party/recipe_engine/config.py b/third_party/recipe_engine/config.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..693b129670fe34442fbcb407598320b61c84d4e4 |
--- /dev/null |
+++ b/third_party/recipe_engine/config.py |
@@ -0,0 +1,736 @@ |
+# Copyright 2013-2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Recipe Configuration Meta DSL. |
+ |
+This module contains, essentially, a DSL for writing composable configurations. |
+You start by defining a schema which describes how your configuration blobs will |
+be structured, and what data they can contain. For example: |
+ |
+ FakeSchema = lambda main_val=True, mode='Happy': ConfigGroup( |
+ config_group = ConfigGroup( |
+ item_a = SimpleConfig(int), |
+ item_b = DictConfig(), |
+ ), |
+ extra_setting = SetConfig(str), |
+ |
+ MAIN_DETERMINANT = StaticConfig(main_val), |
+ CONFIG_MODE = StaticConfig(mode), |
+ ) |
+ |
+In short, a 'schema' is a callable which can take zero arguments (it can contain |
+default arguments as well, for setting defaults, tweaking the schema, etc.), and |
+returning a ConfigGroup. |
+ |
+Every type used in the schema derives from ConfigBase. It has the general |
+characteristics that it's a fixed-type container. It tends to impersonate the |
+data type that it stores (so you can manipulate the config objects like normal |
+python data), but also provides type checking and type conversion assistence |
+(so you can easily render your configurations to JSON). |
+ |
+Then you can create a configuration context: |
+ |
+ config_ctx = config_item_context(FakeSchema) |
+ |
+config_ctx is a python decorator which you can use to create composable |
+configuration functions. For example: |
+ |
+ @config_ctx() |
+ def cool(c): |
+ if c.CONFIG_MODE == 'Happy': |
+ c.config_group.item_a = 100 |
+ else: |
+ c.config_group.item_a = -100 |
+ |
+ @config_ctx() |
+ def gnarly(c): |
+ c.extra_setting = 'gnarly!' |
+ |
+ @config_ctx(includes=('cool', 'gnarly')) |
+ def combo(c): |
+ if c.MAIN_DETERMINANT: |
+ c.config_group.item_b['nickname'] = 'purple' |
+ c.extra_setting += ' cows!' |
+ else: |
+ c.config_group.item_b['nickname'] = 'sad times' |
+ |
+If I now call: |
+ |
+ combo() |
+ |
+I will get back a configuraton object whose schema is FakeSchema, and whose |
+data is the accumulation of cool(), gnarly(), and combo(). I can continue to |
+manipulate this configuraton object, use its data, or render it to json. |
+ |
+Using this system should allow you to create rich, composible, |
+modular configurations. See the documentation on config_item_context and the |
+BaseConfig derivatives for more info. |
+""" |
+ |
+import collections |
+import functools |
+import types |
+ |
+from infra.libs import infra_types |
+ |
+class BadConf(Exception): |
+ pass |
+ |
+def typeAssert(obj, typearg): |
+ if not isinstance(obj, typearg): |
+ raise TypeError("Expected %r to be of type %r" % (obj, typearg)) |
+ |
+ |
+class ConfigContext(object): |
+ """A configuration context for a recipe module. |
+ |
+ Holds configuration schema and also acts as a config_ctx decorator. |
+ A recipe module can define at most one such context. |
+ """ |
+ |
+ def __init__(self, CONFIG_SCHEMA): |
+ self.CONFIG_ITEMS = {} |
+ self.MUTEX_GROUPS = {} |
+ self.CONFIG_SCHEMA = CONFIG_SCHEMA |
+ self.ROOT_CONFIG_ITEM = None |
+ |
+ def __call__(self, group=None, includes=None, deps=None, |
+ is_root=False, config_vars=None): |
+ """ |
+ A decorator for functions which modify a given schema of configs. |
+ Examples continue using the schema and config_items defined in the module |
+ docstring. |
+ |
+ This decorator provides a series of related functions: |
+ * Any function decorated with this will be registered into this config |
+ context by __name__. This enables some of the other following features |
+ to work. |
+ * Alters the signature of the function so that it can receive an extra |
+ parameter 'final'. See the documentation for final on inner(). |
+ * Provides various convenience and error checking facilities. |
+ * In particular, this decorator will prevent you from calling the same |
+ config_ctx on a given config blob more than once (with the exception |
+ of setting final=False. See inner()) |
+ |
+ Args: |
+ group(str) - Using this decorator with the `group' argument causes the |
+ decorated function to be a member of that group. Members of a group are |
+ mutually exclusive on the same configuration blob. For example, only |
+ one of these two functions could be applied to the config blob c: |
+ @config_ctx(group='a') |
+ def bob(c): |
+ c.extra_setting = "bob mode" |
+ |
+ @config_ctx(group='a') |
+ def bill(c): |
+ c.extra_setting = "bill mode" |
+ |
+ includes(iterable(str)) - Any config items named in the includes list will |
+ be run against the config blob before the decorated function can modify |
+ it. If an inclusion is already applied to the config blob, it's skipped |
+ without applying/raising BadConf. Example: |
+ @config_ctx(includes=('bob', 'cool')) |
+ def charlie(c): |
+ c.config_group.item_b = 25 |
+ The result of this config_ctx (assuming default values for the schema) |
+ would be: |
+ {'config_group': { 'item_a': 100, 'item_b': 25 }, |
+ 'extra_setting': 'gnarly!'} |
+ |
+ deps(iterable(str)) - One or more groups which must be satisfied before |
+ this config_ctx can be applied to a config_blob. If you invoke |
+ a config_ctx on a blob without having all of its deps satisfied, |
+ you'll get a BadConf exception. |
+ |
+ is_root(bool) - If set to True on an item, this item will become the |
+ 'basis' item for all other configurations in this group. That means that |
+ it will be implicitly included in all other config_items. There may only |
+ ever be one root item. |
+ |
+ config_vars(dict) - A dictionary mapping of { CONFIG_VAR: <value> }. This |
+ sets the input contidions for the CONFIG_SCHEMA. |
+ |
+ Returns a new decorated version of this function (see inner()). |
+ """ |
+ def decorator(f): |
+ name = f.__name__ |
+ @functools.wraps(f) |
+ def inner(config=None, final=True, optional=False, **kwargs): |
+ """This is the function which is returned from the config_ctx |
+ decorator. |
+ |
+ It applies all of the logic mentioned in the config_ctx docstring |
+ above, and alters the function signature slightly. |
+ |
+ Args: |
+ config - The config blob that we intend to manipulate. This is passed |
+ through to the function after checking deps and including includes. |
+ After the function manipulates it, it is automatically returned. |
+ |
+ final(bool) - Set to True by default, this will record the application |
+ of this config_ctx to `config', which will prevent the config_ctx |
+ from being applied to `config' again. It also is used to see if the |
+ config blob satisfies deps for subsequent config_ctx applications |
+ (i.e. in order for a config_ctx to satisfy a dependency, it must |
+ be applied with final=True). |
+ |
+ This is useful to apply default values while allowing the config to |
+ later override those values. |
+ |
+ However, it's best if each config_ctx is final, because then you |
+ can implement the config items with less error checking, since you |
+ know that the item may only be applied once. For example, if your |
+ item appends something to a list, but is called with final=False, |
+ you'll have to make sure that you don't append the item twice, etc. |
+ |
+ **kwargs - Passed through to the decorated function without harm. |
+ |
+ Returns config and ignores the return value of the decorated function. |
+ """ |
+ if config is None: |
+ config = self.CONFIG_SCHEMA() |
+ assert isinstance(config, ConfigGroup) |
+ inclusions = config._inclusions # pylint: disable=W0212 |
+ |
+ # inner.IS_ROOT will be True or False at the time of invocation. |
+ if (self.ROOT_CONFIG_ITEM and not inner.IS_ROOT and |
+ self.ROOT_CONFIG_ITEM.__name__ not in inclusions): |
+ self.ROOT_CONFIG_ITEM(config) |
+ |
+ if name in inclusions: |
+ if optional: |
+ return config |
+ raise BadConf('config_ctx "%s" is already in this config "%s"' % |
+ (name, config.as_jsonish(include_hidden=True))) |
+ if final: |
+ inclusions.add(name) |
+ |
+ for include in includes or []: |
+ if include in inclusions: |
+ continue |
+ try: |
+ self.CONFIG_ITEMS[include](config) |
+ except BadConf as e: |
+ raise BadConf('config "%s" includes "%s", but [%s]' % |
+ (name, include, e)) |
+ |
+ # deps are a list of group names. All groups must be represented |
+ # in config already. |
+ for dep_group in deps or []: |
+ if not inclusions & self.MUTEX_GROUPS[dep_group]: |
+ raise BadConf('dep group "%s" is unfulfilled for "%s"' % |
+ (dep_group, name)) |
+ |
+ if group: |
+ overlap = inclusions & self.MUTEX_GROUPS[group] |
+ overlap.discard(name) |
+ if overlap: |
+ raise BadConf('"%s" is a member of group "%s", but %s already ran' % |
+ (name, group, tuple(overlap))) |
+ |
+ ret = f(config, **kwargs) |
+ assert ret is None, 'Got return value (%s) from "%s"?' % (ret, name) |
+ |
+ return config |
+ inner.WRAPPED = f |
+ inner.INCLUDES = includes or [] |
+ |
+ def default_config_vars(): |
+ ret = {} |
+ for include in includes or []: |
+ item = self.CONFIG_ITEMS[include] |
+ ret.update(item.DEFAULT_CONFIG_VARS()) |
+ if config_vars: |
+ ret.update(config_vars) |
+ return ret |
+ inner.DEFAULT_CONFIG_VARS = default_config_vars |
+ |
+ assert name not in self.CONFIG_ITEMS, ( |
+ '%s is already in CONFIG_ITEMS' % name) |
+ self.CONFIG_ITEMS[name] = inner |
+ if group: |
+ self.MUTEX_GROUPS.setdefault(group, set()).add(name) |
+ inner.IS_ROOT = is_root |
+ if is_root: |
+ assert not self.ROOT_CONFIG_ITEM, ( |
+ 'may only have one root config_ctx!') |
+ self.ROOT_CONFIG_ITEM = inner |
+ inner.IS_ROOT = True |
+ return inner |
+ return decorator |
+ |
+ |
+def config_item_context(CONFIG_SCHEMA): |
+ """Create a configuration context. |
+ |
+ Args: |
+ CONFIG_SCHEMA: This is a function which can take a minimum of zero arguments |
+ and returns an instance of BaseConfig. This BaseConfig |
+ defines the schema for all configuration objects manipulated |
+ in this context. |
+ |
+ Returns a config_ctx decorator for this context. |
+ """ |
+ return ConfigContext(CONFIG_SCHEMA) |
+ |
+ |
+class AutoHide(object): |
+ pass |
+AutoHide = AutoHide() |
+ |
+ |
+class ConfigBase(object): |
+ """This is the root interface for all config schema types.""" |
+ |
+ def __init__(self, hidden=AutoHide): |
+ """ |
+ Args: |
+ hidden - |
+ True: This object will be excluded from printing when the config blob |
+ is rendered with ConfigGroup.as_jsonish(). You still have full |
+ read/write access to this blob otherwise though. |
+ False: This will be printed as part of ConfigGroup.as_jsonish() |
+ AutoHide: This will be printed as part of ConfigGroup.as_jsonish() only |
+ if self._is_default() is false. |
+ """ |
+ # work around subclasses which override __setattr__ |
+ object.__setattr__(self, '_hidden_mode', hidden) |
+ object.__setattr__(self, '_inclusions', set()) |
+ |
+ def get_val(self): |
+ """Gets the native value of this config object.""" |
+ return self |
+ |
+ def set_val(self, val): |
+ """Resets the value of this config object using data in val.""" |
+ raise NotImplementedError |
+ |
+ def reset(self): |
+ """Resets the value of this config object to it's initial state.""" |
+ raise NotImplementedError |
+ |
+ def as_jsonish(self, include_hidden=False): |
+ """Returns the value of this config object as simple types.""" |
+ raise NotImplementedError |
+ |
+ def complete(self): |
+ """Returns True iff this configuraton blob is fully viable.""" |
+ raise NotImplementedError |
+ |
+ def _is_default(self): |
+ """Returns True iff this configuraton blob is the default value.""" |
+ raise NotImplementedError |
+ |
+ @property |
+ def _hidden(self): |
+ """Returns True iff this configuraton blob is hidden.""" |
+ if self._hidden_mode is AutoHide: |
+ return self._is_default() |
+ return self._hidden_mode |
+ |
+ |
+class ConfigGroup(ConfigBase): |
+ """Allows you to provide hierarchy to a configuration schema. |
+ |
+ Example usage: |
+ config_blob = ConfigGroup( |
+ some_item = SimpleConfig(str), |
+ group = ConfigGroup( |
+ numbahs = SetConfig(int), |
+ ), |
+ ) |
+ config_blob.some_item = "hello" |
+ config_blob.group.numbahs.update(range(10)) |
+ """ |
+ |
+ def __init__(self, hidden=AutoHide, **type_map): |
+ """Expects type_map to be {python_name -> ConfigBase} instance.""" |
+ super(ConfigGroup, self).__init__(hidden) |
+ assert type_map, 'A ConfigGroup with no type_map is meaningless.' |
+ |
+ object.__setattr__(self, '_type_map', type_map) |
+ for name, typeval in self._type_map.iteritems(): |
+ typeAssert(typeval, ConfigBase) |
+ object.__setattr__(self, name, typeval) |
+ |
+ def __getattribute__(self, name): |
+ obj = object.__getattribute__(self, name) |
+ if isinstance(obj, ConfigBase): |
+ return obj.get_val() |
+ else: |
+ return obj |
+ |
+ def __setattr__(self, name, val): |
+ obj = object.__getattribute__(self, name) |
+ typeAssert(obj, ConfigBase) |
+ obj.set_val(val) |
+ |
+ def __delattr__(self, name): |
+ obj = object.__getattribute__(self, name) |
+ typeAssert(obj, ConfigBase) |
+ obj.reset() |
+ |
+ def set_val(self, val): |
+ if isinstance(val, ConfigBase): |
+ val = val.as_jsonish(include_hidden=True) |
+ if isinstance(val, infra_types.FrozenDict): |
+ val = infra_types.thaw(val) |
+ typeAssert(val, dict) |
+ |
+ val = dict(val) # because we pop later. |
+ for name, config_obj in self._type_map.iteritems(): |
+ if name in val: |
+ try: |
+ config_obj.set_val(val.pop(name)) |
+ except Exception as e: |
+ raise Exception('While assigning key %r: %s' % (name, e)) |
+ |
+ if val: |
+ raise TypeError("Got extra keys while setting ConfigGroup: %s" % val) |
+ |
+ def as_jsonish(self, include_hidden=False): |
+ return dict( |
+ (n, v.as_jsonish(include_hidden)) for n, v in self._type_map.iteritems() |
+ if include_hidden or not v._hidden) # pylint: disable=W0212 |
+ |
+ def reset(self): |
+ for v in self._type_map.values(): |
+ v.reset() |
+ |
+ def complete(self): |
+ return all(v.complete() for v in self._type_map.values()) |
+ |
+ def _is_default(self): |
+ # pylint: disable=W0212 |
+ return all(v._is_default() for v in self._type_map.values()) |
+ |
+ |
+class ConfigList(ConfigBase, collections.MutableSequence): |
+ """Allows you to provide an ordered repetition to a configuration schema. |
+ |
+ Example usage: |
+ config_blob = ConfigGroup( |
+ some_items = ConfigList( |
+ lambda: ConfigGroup( |
+ herp = SimpleConfig(int), |
+ derp = SimpleConfig(str) |
+ ) |
+ ) |
+ ) |
+ config_blob.some_items.append({'herp': 1}) |
+ config_blob.some_items[0].derp = 'bob' |
+ """ |
+ |
+ def __init__(self, item_schema, hidden=AutoHide): |
+ """ |
+ Args: |
+ item_schema: The schema of each object. Should be a function which returns |
+ an instance of ConfigGroup. |
+ """ |
+ super(ConfigList, self).__init__(hidden=hidden) |
+ typeAssert(item_schema, types.FunctionType) |
+ typeAssert(item_schema(), ConfigGroup) |
+ self.item_schema = item_schema |
+ self.data = [] |
+ |
+ def __getitem__(self, index): |
+ return self.data.__getitem__(index) |
+ |
+ def __setitem__(self, index, value): |
+ datum = self.item_schema() |
+ datum.set_val(value) |
+ return self.data.__setitem__(index, datum) |
+ |
+ def __delitem__(self, index): |
+ return self.data.__delitem__(index) |
+ |
+ def __len__(self): |
+ return len(self.data) |
+ |
+ def insert(self, index, value): |
+ datum = self.item_schema() |
+ datum.set_val(value) |
+ return self.data.insert(index, datum) |
+ |
+ def add(self): |
+ self.append({}) |
+ return self[-1] |
+ |
+ def reset(self): |
+ self.data = [] |
+ |
+ def complete(self): |
+ return all(i.complete() for i in self.data) |
+ |
+ def set_val(self, data): |
+ if isinstance(data, ConfigList): |
+ data = data.as_jsonish(include_hidden=True) |
+ |
+ typeAssert(data, list) |
+ self.reset() |
+ for item in data: |
+ self.append(item) |
+ |
+ def as_jsonish(self, include_hidden=False): |
+ return [i.as_jsonish(include_hidden) for i in self.data |
+ if include_hidden or not i._hidden] # pylint: disable=W0212 |
+ |
+ def _is_default(self): |
+ # pylint: disable=W0212 |
+ return all(v._is_default() for v in self.data) |
+ |
+ |
+class Dict(ConfigBase, collections.MutableMapping): |
+ """Provides a semi-homogenous dict()-like configuration object.""" |
+ |
+ def __init__(self, item_fn=lambda i: i, jsonish_fn=dict, value_type=None, |
+ hidden=AutoHide): |
+ """ |
+ Args: |
+ item_fn - A function which renders (k, v) pairs to input items for |
+ jsonish_fn. Defaults to the identity function. |
+ jsonish_fn - A function which renders a list of outputs of item_fn to a |
+ JSON-compatiple python datatype. Defaults to dict(). |
+ value_type - A type object used for constraining/validating the values |
+ assigned to this dictionary. |
+ hidden - See ConfigBase. |
+ """ |
+ super(Dict, self).__init__(hidden) |
+ self.value_type = value_type |
+ self.item_fn = item_fn |
+ self.jsonish_fn = jsonish_fn |
+ self.data = {} |
+ |
+ def __getitem__(self, k): |
+ return self.data.__getitem__(k) |
+ |
+ def __setitem__(self, k, v): |
+ if self.value_type: |
+ typeAssert(v, self.value_type) |
+ return self.data.__setitem__(k, v) |
+ |
+ def __delitem__(self, k): |
+ return self.data.__delitem__(k) |
+ |
+ def __iter__(self): |
+ return iter(self.data) |
+ |
+ def __len__(self): |
+ return len(self.data) |
+ |
+ def set_val(self, val): |
+ if isinstance(val, Dict): |
+ val = val.data |
+ if isinstance(val, infra_types.FrozenDict): |
+ val = dict(val) |
+ |
+ typeAssert(val, dict) |
+ for v in val.itervalues(): |
+ typeAssert(v, self.value_type) |
+ self.data = val |
+ |
+ def as_jsonish(self, _include_hidden=None): |
+ return self.jsonish_fn(map( |
+ self.item_fn, sorted(self.data.iteritems(), key=lambda x: x[0]))) |
+ |
+ def reset(self): |
+ self.data.clear() |
+ |
+ def complete(self): |
+ return True |
+ |
+ def _is_default(self): |
+ return not self.data |
+ |
+ |
+class List(ConfigBase, collections.MutableSequence): |
+ """Provides a semi-homogenous list()-like configuration object.""" |
+ |
+ def __init__(self, inner_type, jsonish_fn=list, hidden=AutoHide): |
+ """ |
+ Args: |
+ inner_type - The type of data contained in this set, e.g. str, int, ... |
+ Can also be a tuple of types to allow more than one type. |
+ jsonish_fn - A function used to reduce the list() to a JSON-compatible |
+ python datatype. Defaults to list(). |
+ hidden - See ConfigBase. |
+ """ |
+ super(List, self).__init__(hidden) |
+ self.inner_type = inner_type |
+ self.jsonish_fn = jsonish_fn |
+ self.data = [] |
+ |
+ def __getitem__(self, index): |
+ return self.data[index] |
+ |
+ def __setitem__(self, index, value): |
+ typeAssert(value, self.inner_type) |
+ self.data[index] = value |
+ |
+ def __delitem__(self, index): |
+ del self.data |
+ |
+ def __len__(self): |
+ return len(self.data) |
+ |
+ def __radd__(self, other): |
+ if not isinstance(other, list): |
+ other = list(other) |
+ return other + self.data |
+ |
+ def insert(self, index, value): |
+ typeAssert(value, self.inner_type) |
+ self.data.insert(index, value) |
+ |
+ def set_val(self, val): |
+ for v in val: |
+ typeAssert(v, self.inner_type) |
+ self.data = list(val) |
+ |
+ def as_jsonish(self, _include_hidden=None): |
+ return self.jsonish_fn(self.data) |
+ |
+ def reset(self): |
+ self.data = [] |
+ |
+ def complete(self): |
+ return True |
+ |
+ def _is_default(self): |
+ return not self.data |
+ |
+ |
+class Set(ConfigBase, collections.MutableSet): |
+ """Provides a semi-homogenous set()-like configuration object.""" |
+ |
+ def __init__(self, inner_type, jsonish_fn=list, hidden=AutoHide): |
+ """ |
+ Args: |
+ inner_type - The type of data contained in this set, e.g. str, int, ... |
+ Can also be a tuple of types to allow more than one type. |
+ jsonish_fn - A function used to reduce the set() to a JSON-compatible |
+ python datatype. Defaults to list(). |
+ hidden - See ConfigBase. |
+ """ |
+ super(Set, self).__init__(hidden) |
+ self.inner_type = inner_type |
+ self.jsonish_fn = jsonish_fn |
+ self.data = set() |
+ |
+ def __contains__(self, val): |
+ return val in self.data |
+ |
+ def __iter__(self): |
+ return iter(self.data) |
+ |
+ def __len__(self): |
+ return len(self.data) |
+ |
+ def add(self, value): |
+ typeAssert(value, self.inner_type) |
+ self.data.add(value) |
+ |
+ def update(self, values): |
+ for value in values: |
+ if value not in self: |
+ self.add(value) |
+ |
+ def discard(self, value): |
+ self.data.discard(value) |
+ |
+ def set_val(self, val): |
+ for v in val: |
+ typeAssert(v, self.inner_type) |
+ self.data = set(val) |
+ |
+ def as_jsonish(self, _include_hidden=None): |
+ return self.jsonish_fn(sorted(self.data)) |
+ |
+ def reset(self): |
+ self.data = set() |
+ |
+ def complete(self): |
+ return True |
+ |
+ def _is_default(self): |
+ return not self.data |
+ |
+ |
+class Single(ConfigBase): |
+ """Provides a configuration object which holds a single 'simple' type.""" |
+ |
+ def __init__(self, inner_type, jsonish_fn=lambda x: x, empty_val=None, |
+ required=True, hidden=AutoHide): |
+ """ |
+ Args: |
+ inner_type - The type of data contained in this object, e.g. str, int, ... |
+ Can also be a tuple of types to allow more than one type. |
+ jsonish_fn - A function used to reduce the data to a JSON-compatible |
+ python datatype. Default is the identity function. |
+ empty_val - The value to use when initializing this object or when calling |
+ reset(). |
+ required(bool) - True iff this config item is required to have a |
+ non-empty_val in order for it to be considered complete(). |
+ hidden - See ConfigBase. |
+ """ |
+ super(Single, self).__init__(hidden) |
+ self.inner_type = inner_type |
+ self.jsonish_fn = jsonish_fn |
+ self.empty_val = empty_val |
+ self.data = empty_val |
+ self.required = required |
+ |
+ def get_val(self): |
+ return self.data |
+ |
+ def set_val(self, val): |
+ if isinstance(val, Single): |
+ val = val.data |
+ if val is not self.empty_val: |
+ typeAssert(val, self.inner_type) |
+ self.data = val |
+ |
+ def as_jsonish(self, _include_hidden=None): |
+ return self.jsonish_fn(self.data) |
+ |
+ def reset(self): |
+ self.data = self.empty_val |
+ |
+ def complete(self): |
+ return not self.required or self.data is not self.empty_val |
+ |
+ def _is_default(self): |
+ return self.data is self.empty_val |
+ |
+ |
+class Static(ConfigBase): |
+ """Holds a single, hidden, immutible data object. |
+ |
+ This is very useful for holding the 'input' configuration values. |
+ """ |
+ |
+ def __init__(self, value, hidden=AutoHide): |
+ super(Static, self).__init__(hidden=hidden) |
+ # Attempt to hash the value, which will ensure that it's immutable all the |
+ # way down :). |
+ hash(value) |
+ self.data = value |
+ |
+ def get_val(self): |
+ return self.data |
+ |
+ def set_val(self, val): |
+ raise TypeError("Cannot assign to a Static config member") |
+ |
+ def as_jsonish(self, _include_hidden=None): |
+ return self.data |
+ |
+ def reset(self): |
+ assert False |
+ |
+ def complete(self): |
+ return True |
+ |
+ def _is_default(self): |
+ return True |