22 from __future__
import print_function
23 from builtins
import str
24 from builtins
import object
29 from .config
import Config, Field, _joinNamePath, _typeStr, FieldValidationError
30 from .comparison
import compareConfigs, getComparisonName
34 def __initValue(self, at, label):
36 if field.default is an instance of ConfigClass, custom construct
37 _value with the correct values from default.
38 otherwise call ConfigClass constructor
40 name = _joinNamePath(self._config._name, self._field.name)
42 storage = self._field.default._storage
45 value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
46 object.__setattr__(self,
"_value", value)
48 def __init__(self, config, field, at=None, label="default"):
49 object.__setattr__(self,
"_config", config)
50 object.__setattr__(self,
"_field", field)
51 object.__setattr__(self,
"__doc__", config)
52 object.__setattr__(self,
"_target", field.target)
53 object.__setattr__(self,
"_ConfigClass", field.ConfigClass)
54 object.__setattr__(self,
"_value",
None)
57 at = traceback.extract_stack()[:-1]
58 at += [self._field.source]
61 history = config._history.setdefault(field.name, [])
62 history.append((
"Targeted and initialized from defaults", at, label))
65 Read-only access to the targeted configurable
67 target = property(
lambda x: x._target)
69 Read-only access to the ConfigClass
71 ConfigClass = property(
lambda x: x._ConfigClass)
74 Read-only access to the ConfigClass instance
76 value = property(
lambda x: x._value)
80 Call the confirurable.
81 With argument config=self.value along with any positional and kw args
86 Target a new configurable and ConfigClass
88 def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
89 if self._config._frozen:
90 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
93 ConfigClass = self._field.validateTarget(target, ConfigClass)
94 except BaseException
as e:
95 raise FieldValidationError(self._field, self._config, e.message)
98 at = traceback.extract_stack()[:-1]
99 object.__setattr__(self,
"_target", target)
101 object.__setattr__(self,
"_ConfigClass", ConfigClass)
104 history = self._config._history.setdefault(self._field.name, [])
105 msg =
"retarget(target=%s, ConfigClass=%s)" % (_typeStr(target), _typeStr(ConfigClass))
106 history.append((msg, at, label))
109 return getattr(self._value, name)
113 Pretend to be an isntance of ConfigClass.
114 Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
116 if self._config._frozen:
117 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
119 if name
in self.__dict__:
121 object.__setattr__(self, name, value)
124 at = traceback.extract_stack()[:-1]
125 self._value.__setattr__(name, value, at=at, label=label)
129 Pretend to be an isntance of ConfigClass.
130 Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
132 if self._config._frozen:
133 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
137 object.__delattr__(self, name)
138 except AttributeError:
140 at = traceback.extract_stack()[:-1]
141 self._value.__delattr__(name, at=at, label=label)
146 A variant of a ConfigField which has a known configurable target
148 Behaves just like a ConfigField except that it can be 'retargeted' to point
149 at a different configurable. Further you can 'apply' to construct a fully
150 configured configurable.
156 if ConfigClass
is None:
158 ConfigClass = target.ConfigClass
160 raise AttributeError(
"'target' must define attribute 'ConfigClass'")
161 if not issubclass(ConfigClass, Config):
162 raise TypeError(
"'ConfigClass' is of incorrect type %s."
163 "'ConfigClass' must be a subclass of Config" % _typeStr(ConfigClass))
164 if not hasattr(target,
'__call__'):
165 raise ValueError(
"'target' must be callable")
166 if not hasattr(target,
'__module__')
or not hasattr(target,
'__name__'):
167 raise ValueError(
"'target' must be statically defined"
168 "(must have '__module__' and '__name__' attributes)")
171 def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
173 @param target is the configurable target. Must be callable, and the first
174 parameter will be the value of this field
175 @param ConfigClass is the class of Config object expected by the target.
176 If not provided by target.ConfigClass it must be provided explicitly in this argument
181 default = ConfigClass
182 if default != ConfigClass
and type(default) != ConfigClass:
183 raise TypeError(
"'default' is of incorrect type %s. Expected %s" %
184 (_typeStr(default), _typeStr(ConfigClass)))
186 source = traceback.extract_stack(limit=2)[0]
187 self._setup(doc=doc, dtype=ConfigurableInstance, default=default,
188 check=check, optional=
False, source=source)
192 def __getOrMake(self, instance, at=None, label="default"):
193 value = instance._storage.get(self.name,
None)
196 at = traceback.extract_stack()[:-2]
198 instance._storage[self.name] = value
201 def __get__(self, instance, owner=None, at=None, label="default"):
202 if instance
is None or not isinstance(instance, Config):
205 return self.
__getOrMake(instance, at=at, label=label)
207 def __set__(self, instance, value, at=None, label="assignment"):
209 raise FieldValidationError(self, instance,
"Cannot modify a frozen Config")
211 at = traceback.extract_stack()[:-1]
214 if isinstance(value, ConfigurableInstance):
215 oldValue.retarget(value.target, value.ConfigClass, at, label)
216 oldValue.update(__at=at, __label=label, **value._storage)
217 elif type(value) == oldValue._ConfigClass:
218 oldValue.update(__at=at, __label=label, **value._storage)
219 elif value == oldValue.ConfigClass:
220 value = oldValue.ConfigClass()
221 oldValue.update(__at=at, __label=label, **value._storage)
223 msg =
"Value %s is of incorrect type %s. Expected %s" % \
224 (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
225 raise FieldValidationError(self, instance, msg)
228 fullname = _joinNamePath(instance._name, self.name)
230 value._rename(fullname)
232 def save(self, outfile, instance):
233 fullname = _joinNamePath(instance._name, self.name)
235 target = value.target
240 ConfigClass = value.ConfigClass
241 for module
in set([target.__module__, ConfigClass.__module__]):
242 outfile.write(
u"import {}\n".
format(module))
243 outfile.write(
u"{}.retarget(target={}, ConfigClass={})\n\n".
format(fullname,
245 _typeStr(ConfigClass)))
255 return value.toDict()
261 if self.check
is not None and not self.check(value):
262 msg =
"%s is not a valid value" % str(value)
263 raise FieldValidationError(self, instance, msg)
266 """Customize deep-copying, because we always want a reference to the original typemap.
268 WARNING: this must be overridden by subclasses if they change the constructor signature!
270 return type(self)(doc=self.doc, target=self.
target, ConfigClass=self.
ConfigClass,
271 default=copy.deepcopy(self.default))
273 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
274 """Helper function for Config.compare; used to compare two fields for equality.
276 @param[in] instance1 LHS Config instance to compare.
277 @param[in] instance2 RHS Config instance to compare.
278 @param[in] shortcut If True, return as soon as an inequality is found.
279 @param[in] rtol Relative tolerance for floating point comparisons.
280 @param[in] atol Absolute tolerance for floating point comparisons.
281 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
282 to report inequalities.
284 Floating point comparisons are performed by numpy.allclose; refer to that for details.
286 c1 = getattr(instance1, self.name)._value
287 c2 = getattr(instance2, self.name)._value
289 _joinNamePath(instance1._name, self.name),
290 _joinNamePath(instance2._name, self.name)
292 return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)