23 __all__ = [
"ConfigChoiceField"]
26 import collections.abc
28 from .config
import Config, Field, FieldValidationError, _typeStr, _joinNamePath
29 from .comparison
import getComparisonName, compareScalars, compareConfigs
30 from .callStack
import getCallStack, getStackFrame
35 Custom set class used to track the selection of multi-select 38 This class allows user a multi-select ConfigChoiceField to add/discard 39 items from the set of active configs. Each change to the selection is 40 tracked in the field's history. 42 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
52 if v
not in self.
_dict:
54 self.
_dict.__getitem__(v, at=at)
56 msg =
"Value %s is of incorrect type %s. Sequence type expected"(value, _typeStr(value))
58 self.
_set = set(value)
63 self.
__history.append((
"Set selection to %s" % self, at, label))
65 def add(self, value, at=None):
68 "Cannot modify a frozen Config")
73 if value
not in self.
_dict:
75 self.
_dict.__getitem__(value, at=at)
77 self.
__history.append((
"added %s to selection" % value, at,
"selection"))
83 "Cannot modify a frozen Config")
85 if value
not in self.
_dict:
91 self.
__history.append((
"removed %s from selection" % value, at,
"selection"))
98 return iter(self.
_set)
101 return value
in self.
_set 104 return repr(list(self.
_set))
107 return str(list(self.
_set))
111 """A dict of instantiated configs, used to populate a ConfigChoiceField. 113 typemap must support the following: 114 - typemap[name]: return the config class associated with the given name 117 collections.abc.Mapping.__init__(self)
122 self.
_history = config._history.setdefault(field.name, [])
125 types = property(
lambda x: x._field.typemap)
128 return k
in self.
_field.typemap
131 return len(self.
_field.typemap)
134 return iter(self.
_field.typemap)
136 def _setSelection(self, value, at=None, label="assignment"):
148 if value
not in self.
_dict:
151 self.
_history.append((value, at, label))
156 "Single-selection field has no attribute 'names'")
159 def _setNames(self, value):
162 "Single-selection field has no attribute 'names'")
168 "Single-selection field has no attribute 'names'")
174 "Multi-selection field has no attribute 'name'")
177 def _setName(self, value):
180 "Multi-selection field has no attribute 'name'")
186 "Multi-selection field has no attribute 'name'")
190 In a multi-selection ConfigInstanceDict, list of names of active items 191 Disabled In a single-selection _Regsitry) 193 names = property(_getNames, _setNames, _delNames)
196 In a single-selection ConfigInstanceDict, name of the active item 197 Disabled In a multi-selection _Regsitry) 199 name = property(_getName, _setName, _delName)
201 def _getActive(self):
211 Readonly shortcut to access the selected item(s) 212 for multi-selection, this is equivalent to: [self[name] for name in self.names] 213 for single-selection, this is equivalent to: self[name] 215 active = property(_getActive)
219 value = self.
_dict[k]
222 dtype = self.
_field.typemap[k]
225 "Unknown key %r in Registry/ConfigChoiceField" % k)
226 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
229 at.insert(0, dtype._source)
230 value = self.
_dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
238 dtype = self.
_field.typemap[k]
242 if value != dtype
and type(value) != dtype:
243 msg =
"Value %s at key %k is of incorrect type %s. Expected type %s" % \
244 (value, k, _typeStr(value), _typeStr(dtype))
249 name = _joinNamePath(self.
_config._name, self.
_field.name, k)
250 oldValue = self.
_dict.get(k,
None)
253 self.
_dict[k] = value(__name=name, __at=at, __label=label)
255 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
259 oldValue.update(__at=at, __label=label, **value._storage)
261 def _rename(self, fullname):
262 for k, v
in self.
_dict.items():
263 v._rename(_joinNamePath(name=fullname, index=k))
266 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
268 object.__setattr__(self, attr, value)
269 elif attr
in self.__dict__
or attr
in [
"_history",
"_field",
"_config",
"_dict",
270 "_selection",
"__doc__"]:
272 object.__setattr__(self, attr, value)
275 msg =
"%s has no attribute %s" % (_typeStr(self.
_field), attr)
281 ConfigChoiceFields allow the config to choose from a set of possible Config types. 282 The set of allowable types is given by the typemap argument to the constructor 284 The typemap object must implement typemap[name], which must return a Config subclass. 286 While the typemap is shared by all instances of the field, each instance of 287 the field has its own instance of a particular sub-config type 291 class AaaConfig(Config): 292 somefield = Field(int, "...") 293 TYPEMAP = {"A", AaaConfig} 294 class MyConfig(Config): 295 choice = ConfigChoiceField("doc for choice", TYPEMAP) 297 instance = MyConfig() 298 instance.choice['AAA'].somefield = 5 299 instance.choice = "AAA" 301 Alternatively, the last line can be written: 302 instance.choice.name = "AAA" 304 Validation of this field is performed only the "active" selection. 305 If active is None and the field is not optional, validation will fail. 307 ConfigChoiceFields can allow single selections or multiple selections. 308 Single selection fields set selection through property name, and 309 multi-selection fields use the property names. 311 ConfigChoiceFields also allow multiple values of the same type: 312 TYPEMAP["CCC"] = AaaConfig 313 TYPEMAP["BBB"] = AaaConfig 315 When saving a config with a ConfigChoiceField, the entire set is saved, as well as the active selection 317 instanceDictClass = ConfigInstanceDict
319 def __init__(self, doc, typemap, default=None, optional=False, multi=False):
326 def _getOrMake(self, instance, label="default"):
327 instanceDict = instance._storage.get(self.name)
328 if instanceDict
is None:
330 instanceDict = self.
dtype(instance, self)
331 instanceDict.__doc__ = self.
doc 332 instance._storage[self.name] = instanceDict
333 history = instance._history.setdefault(self.name, [])
334 history.append((
"Initialized from defaults", at, label))
339 if instance
is None or not isinstance(instance, Config):
344 def __set__(self, instance, value, at=None, label="assignment"):
351 for k, v
in value.items():
352 instanceDict.__setitem__(k, v, at=at, label=label)
353 instanceDict._setSelection(value._selection, at=at, label=label)
356 instanceDict._setSelection(value, at=at, label=label)
359 instanceDict = self.
__get__(instance)
360 fullname = _joinNamePath(instance._name, self.name)
361 instanceDict._rename(fullname)
364 instanceDict = self.
__get__(instance)
365 if instanceDict.active
is None and not self.
optional:
366 msg =
"Required field cannot be None" 368 elif instanceDict.active
is not None:
370 for a
in instanceDict.active:
373 instanceDict.active.validate()
376 instanceDict = self.
__get__(instance)
380 dict_[
"names"] = instanceDict.names
382 dict_[
"name"] = instanceDict.name
385 for k, v
in instanceDict.items():
386 values[k] = v.toDict()
387 dict_[
"values"] = values
392 instanceDict = self.
__get__(instance)
393 for v
in instanceDict.values():
396 def save(self, outfile, instance):
397 instanceDict = self.
__get__(instance)
398 fullname = _joinNamePath(instance._name, self.name)
399 for v
in instanceDict.values():
402 outfile.write(
u"{}.names={!r}\n".
format(fullname, instanceDict.names))
404 outfile.write(
u"{}.name={!r}\n".
format(fullname, instanceDict.name))
407 """Customize deep-copying, because we always want a reference to the original typemap. 409 WARNING: this must be overridden by subclasses if they change the constructor signature! 411 other = type(self)(doc=self.
doc, typemap=self.
typemap, default=copy.deepcopy(self.
default),
413 other.source = self.
source 416 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
417 """Helper function for Config.compare; used to compare two fields for equality. 419 Only the selected config(s) are compared, as the parameters of any others do not matter. 421 @param[in] instance1 LHS Config instance to compare. 422 @param[in] instance2 RHS Config instance to compare. 423 @param[in] shortcut If True, return as soon as an inequality is found. 424 @param[in] rtol Relative tolerance for floating point comparisons. 425 @param[in] atol Absolute tolerance for floating point comparisons. 426 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly) 427 to report inequalities. 429 Floating point comparisons are performed by numpy.allclose; refer to that for details. 431 d1 = getattr(instance1, self.name)
432 d2 = getattr(instance2, self.name)
434 _joinNamePath(instance1._name, self.name),
435 _joinNamePath(instance2._name, self.name)
437 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
439 if d1._selection
is None:
442 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
444 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
446 for k, c1, c2
in nested:
447 result =
compareConfigs(
"%s[%r]" % (name, k), c1, c2, shortcut=shortcut,
448 rtol=rtol, atol=atol, output=output)
449 if not result
and shortcut:
451 equal = equal
and result
def __init__(self, doc, typemap, default=None, optional=False, multi=False)
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
def __set__(self, instance, value, at=None, label="assignment")
def __contains__(self, k)
def save(self, outfile, instance)
def __get__(self, instance, owner=None, at=None, label="default")
def __setitem__(self, k, value, at=None, label="assignment")
def _setup(self, doc, dtype, default, check, optional, source)
def add(self, value, at=None)
def toDict(self, instance)
def getStackFrame(relative=0)
def _getOrMake(self, instance, label="default")
def freeze(self, instance)
def __getitem__(self, k, at=None, label="default")
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def validate(self, instance)
def __get__(self, instance, owner=None)
def discard(self, value, at=None)
def compareScalars(name, v1, v2, output, rtol=1E-8, atol=1E-8, dtype=None)
def __deepcopy__(self, memo)
def __init__(self, config, field)
def __contains__(self, value)
def _setSelection(self, value, at=None, label="assignment")
def __init__(self, dict_, value, at=None, label="assignment", setHistory=True)
def rename(self, instance)
def getComparisonName(name1, name2)
def __setattr__(self, attr, value, at=None, label="assignment")