22 from builtins
import str
26 from .config
import Config, Field, FieldValidationError, _joinNamePath, _typeStr
27 from .comparison
import compareConfigs, getComparisonName
29 __all__ = [
"ConfigField"]
34 Defines a field which is itself a Config.
36 The behavior of this type of field is much like that of the base Field type.
38 Note that dtype must be a subclass of Config.
40 If default=None, the field will default to a default-constructed
43 Additionally, to allow for fewer deep-copies, assigning an instance of
44 ConfigField to dtype itself, is considered equivalent to assigning a
45 default-constructed sub-config. This means that the argument default can be
46 dtype, as well as an instance of dtype.
48 Assigning to ConfigField will update all of the fields in the config.
51 def __init__(self, doc, dtype, default=None, check=None):
52 if not issubclass(dtype, Config):
53 raise ValueError(
"dtype=%s is not a subclass of Config" %
57 source = traceback.extract_stack(limit=2)[0]
58 self._setup(doc=doc, dtype=dtype, default=default, check=check,
59 optional=
False, source=source)
62 if instance
is None or not isinstance(instance, Config):
65 value = instance._storage.get(self.name,
None)
67 at = traceback.extract_stack()[:-1]+[self.source]
68 self.
__set__(instance, self.default, at=at, label=
"default")
71 def __set__(self, instance, value, at=None, label="assignment"):
73 raise FieldValidationError(self, instance,
74 "Cannot modify a frozen Config")
75 name = _joinNamePath(prefix=instance._name, name=self.name)
77 if value != self.dtype
and type(value) != self.dtype:
78 msg =
"Value %s is of incorrect type %s. Expected %s" % \
79 (value, _typeStr(value), _typeStr(self.dtype))
80 raise FieldValidationError(self, instance, msg)
83 at = traceback.extract_stack()[:-1]
85 oldValue = instance._storage.get(self.name,
None)
87 if value == self.dtype:
88 instance._storage[self.name] = self.dtype(__name=name, __at=at, __label=label)
90 instance._storage[self.name] = self.dtype(__name=name, __at=at,
91 __label=label, **value._storage)
93 if value == self.dtype:
95 oldValue.update(__at=at, __label=label, **value._storage)
96 history = instance._history.setdefault(self.name, [])
97 history.append((
"config value set", at, label))
101 value._rename(_joinNamePath(instance._name, self.name))
103 def save(self, outfile, instance):
113 return value.toDict()
119 if self.check
is not None and not self.check(value):
120 msg =
"%s is not a valid value" % str(value)
121 raise FieldValidationError(self, instance, msg)
123 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
124 """Helper function for Config.compare; used to compare two fields for equality.
126 @param[in] instance1 LHS Config instance to compare.
127 @param[in] instance2 RHS Config instance to compare.
128 @param[in] shortcut If True, return as soon as an inequality is found.
129 @param[in] rtol Relative tolerance for floating point comparisons.
130 @param[in] atol Absolute tolerance for floating point comparisons.
131 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
132 to report inequalities.
134 Floating point comparisons are performed by numpy.allclose; refer to that for details.
136 c1 = getattr(instance1, self.name)
137 c2 = getattr(instance2, self.name)
139 _joinNamePath(instance1._name, self.name),
140 _joinNamePath(instance2._name, self.name)
142 return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)