22 import traceback, copy, collections
25 from .config
import Config, Field, FieldValidationError, _typeStr, _joinNamePath
26 from .comparison
import *
28 __all__ = [
"ConfigChoiceField"]
32 Custom set class used to track the selection of multi-select
35 This class allows user a multi-select ConfigChoiceField to add/discard
36 items from the set of active configs. Each change to the selection is
37 tracked in the field's history.
39 def __init__(self, dict_, value, at=None, label="assignment", setHistory=True):
41 at = traceback.extract_stack()[:-1]
45 self.
__history = self._config._history.setdefault(self._field.name, [])
49 if v
not in self.
_dict:
51 r = self._dict.__getitem__(v, at=at)
53 msg =
"Value %s is of incorrect type %s. Sequence type expected"(value,
_typeStr(value))
60 self.__history.append((
"Set selection to %s"%self, at, label))
62 def add(self, value, at= None):
63 if self._config._frozen:
65 "Cannot modify a frozen Config")
68 at = traceback.extract_stack()[:-1]
70 if value
not in self.
_dict:
72 r = self.__getitem__(value, at=at)
74 self.__history.append((
"added %s to selection"%value, at,
"selection"))
78 if self._config._frozen:
80 "Cannot modify a frozen Config")
82 if value
not in self.
_dict:
86 at = traceback.extract_stack()[:-1]
88 self.__history.append((
"removed %s from selection"%value, at,
"selection"))
89 self._set.discard(value)
99 """A dict of instantiated configs, used to populate a ConfigChoiceField.
101 typemap must support the following:
102 - typemap[name]: return the config class associated with the given name
105 collections.Mapping.__init__(self)
110 self.
_history = config._history.setdefault(field.name, [])
113 types = property(
lambda x: x._field.typemap)
117 def __len__(self):
return len(self._field.typemap)
119 def __iter__(self):
return iter(self._field.typemap)
122 if self._config._frozen:
123 raise FieldValidationError(self.
_field, self.
_config,
"Cannot modify a frozen Config")
126 at = traceback.extract_stack()[:-2]
130 elif self._field.multi:
133 if value
not in self.
_dict:
136 self._history.append((value, at, label))
139 if not self._field.multi:
141 "Single-selection field has no attribute 'names'")
144 if not self._field.multi:
146 "Single-selection field has no attribute 'names'")
149 if not self._field.multi:
151 "Single-selection field has no attribute 'names'")
155 if self._field.multi:
157 "Multi-selection field has no attribute 'name'")
160 if self._field.multi:
162 "Multi-selection field has no attribute 'name'")
165 if self._field.multi:
167 "Multi-selection field has no attribute 'name'")
171 In a multi-selection ConfigInstanceDict, list of names of active items
172 Disabled In a single-selection _Regsitry)
174 names = property(_getNames, _setNames, _delNames)
177 In a single-selection ConfigInstanceDict, name of the active item
178 Disabled In a multi-selection _Regsitry)
180 name = property(_getName, _setName, _delName)
186 if self._field.multi:
192 Readonly shortcut to access the selected item(s)
193 for multi-selection, this is equivalent to: [self[name] for name in self.names]
194 for single-selection, this is equivalent to: self[name]
196 active = property(_getActive)
200 value = self.
_dict[k]
203 dtype = self._field.typemap[k]
205 raise FieldValidationError(self.
_field, self.
_config,
"Unknown key %r"%k)
206 name =
_joinNamePath(self._config._name, self._field.name, k)
208 at = traceback.extract_stack()[:-1] + [dtype._source]
209 value = self._dict.setdefault(k, dtype(__name=name, __at=at, __label=label))
213 if self._config._frozen:
214 raise FieldValidationError(self.
_field, self.
_config,
"Cannot modify a frozen Config")
217 dtype = self._field.typemap[k]
219 raise FieldValidationError(self.
_field, self.
_config,
"Unknown key %r"%k)
221 if value != dtype
and type(value) != dtype:
222 msg =
"Value %s at key %k is of incorrect type %s. Expected type %s"%\
227 at = traceback.extract_stack()[:-1]
228 name =
_joinNamePath(self._config._name, self._field.name, k)
229 oldValue = self._dict.get(k,
None)
232 self.
_dict[k] = value(__name=name, __at=at, __label=label)
234 self.
_dict[k] = dtype(__name=name, __at=at, __label=label, **value._storage)
238 oldValue.update(__at=at, __label=label, **value._storage)
241 for k, v
in self._dict.iteritems():
245 if hasattr(getattr(self.__class__, attr,
None),
'__set__'):
247 object.__setattr__(self, attr, value)
248 elif attr
in self.__dict__
or attr
in [
"_history",
"_field",
"_config",
"_dict",
"_selection",
"__doc__"]:
250 object.__setattr__(self, attr, value)
261 ConfigChoiceFields allow the config to choose from a set of possible Config types.
262 The set of allowable types is given by the typemap argument to the constructor
264 The typemap object must implement typemap[name], which must return a Config subclass.
266 While the typemap is shared by all instances of the field, each instance of
267 the field has its own instance of a particular sub-config type
271 class AaaConfig(Config):
272 somefield = Field(int, "...")
273 TYPEMAP = {"A", AaaConfig}
274 class MyConfig(Config):
275 choice = ConfigChoiceField("doc for choice", TYPEMAP)
277 instance = MyConfig()
278 instance.choice['AAA'].somefield = 5
279 instance.choice = "AAA"
281 Alternatively, the last line can be written:
282 instance.choice.name = "AAA"
284 Validation of this field is performed only the "active" selection.
285 If active is None and the field is not optional, validation will fail.
287 ConfigChoiceFields can allow single selections or multiple selections.
288 Single selection fields set selection through property name, and
289 multi-selection fields use the property names.
291 ConfigChoiceFields also allow multiple values of the same type:
292 TYPEMAP["CCC"] = AaaConfig
293 TYPEMAP["BBB"] = AaaConfig
295 When saving a config with a ConfigChoiceField, the entire set is saved, as well as the active selection
297 instanceDictClass = ConfigInstanceDict
298 def __init__(self, doc, typemap, default=None, optional=False, multi=False):
299 source = traceback.extract_stack(limit=2)[0]
300 self._setup( doc=doc, dtype=self.
instanceDictClass, default=default, check=
None, optional=optional, source=source)
305 instanceDict = instance._storage.get(self.name)
306 if instanceDict
is None:
307 at = traceback.extract_stack()[:-2]
309 instanceDict = self.dtype(instance, self)
310 instanceDict.__doc__ = self.doc
311 instance._storage[self.name] = instanceDict
312 history = instance._history.setdefault(self.name, [])
313 history.append((
"Initialized from defaults", at, label))
318 if instance
is None or not isinstance(instance, Config):
323 def __set__(self, instance, value, at=None, label="assignment"):
325 raise FieldValidationError(self, instance,
"Cannot modify a frozen Config")
327 at = traceback.extract_stack()[:-1]
330 for k,v
in value.iteritems():
331 instanceDict.__setitem__(k, v, at=at, label=label)
332 instanceDict._setSelection(value._selection, at=at, label=label)
335 instanceDict._setSelection(value, at=at, label=label)
338 instanceDict = self.
__get__(instance)
340 instanceDict._rename(fullname)
343 instanceDict = self.
__get__(instance)
344 if instanceDict.active
is None and not self.optional:
345 msg =
"Required field cannot be None"
346 raise FieldValidationError(self, instance, msg)
347 elif instanceDict.active
is not None:
349 for a
in instanceDict.active:
352 instanceDict.active.validate()
355 instanceDict = self.
__get__(instance)
359 dict_[
"names"]=instanceDict.names
361 dict_[
"name"] =instanceDict.name
364 for k, v
in instanceDict.iteritems():
366 dict_[
"values"]=values
371 instanceDict = self.
__get__(instance)
372 for v
in instanceDict.itervalues():
375 def save(self, outfile, instance):
376 instanceDict = self.
__get__(instance)
378 for v
in instanceDict.itervalues():
381 print >> outfile,
"%s.names=%r"%(fullname, instanceDict.names)
383 print >> outfile,
"%s.name=%r"%(fullname, instanceDict.name)
386 """Customize deep-copying, because we always want a reference to the original typemap.
388 WARNING: this must be overridden by subclasses if they change the constructor signature!
390 other = type(self)(doc=self.doc, typemap=self.
typemap, default=copy.deepcopy(self.default),
391 optional=self.optional, multi=self.
multi)
392 other.source = self.source
395 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
396 """Helper function for Config.compare; used to compare two fields for equality.
398 Only the selected config(s) are compared, as the parameters of any others do not matter.
400 @param[in] instance1 LHS Config instance to compare.
401 @param[in] instance2 RHS Config instance to compare.
402 @param[in] shortcut If True, return as soon as an inequality is found.
403 @param[in] rtol Relative tolerance for floating point comparisons.
404 @param[in] atol Absolute tolerance for floating point comparisons.
405 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
406 to report inequalities.
408 Floating point comparisons are performed by numpy.allclose; refer to that for details.
410 d1 = getattr(instance1, self.name)
411 d2 = getattr(instance2, self.name)
416 if not compareScalars(
"selection for %s" % name, d1._selection, d2._selection, output=output):
418 if d1._selection
is None:
421 nested = [(k, d1[k], d2[k])
for k
in d1._selection]
423 nested = [(d1._selection, d1[d1._selection], d2[d1._selection])]
425 for k, c1, c2
in nested:
426 result =
compareConfigs(
"%s[%r]" % (name, k), c1, c2, shortcut=shortcut,
427 rtol=rtol, atol=atol, output=output)
428 if not result
and shortcut:
430 equal = equal
and result