22 import traceback, copy
24 from .config
import Config, Field, _joinNamePath, _typeStr, FieldValidationError
25 from .comparison
import compareConfigs, getComparisonName
30 if field.default is an instance of ConfigClass, custom construct
31 _value with the correct values from default.
32 otherwise call ConfigClass constructor
36 storage = self._field.default._storage
39 value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
40 object.__setattr__(self,
"_value", value)
42 def __init__(self, config, field, at=None, label="default"):
43 object.__setattr__(self,
"_config", config)
44 object.__setattr__(self,
"_field", field)
45 object.__setattr__(self,
"__doc__", config)
46 object.__setattr__(self,
"_target", field.target)
47 object.__setattr__(self,
"_ConfigClass",field.ConfigClass)
48 object.__setattr__(self,
"_value",
None)
51 at = traceback.extract_stack()[:-1]
52 at += [self._field.source]
55 history = config._history.setdefault(field.name, [])
56 history.append((
"Targeted and initialized from defaults", at, label))
59 Read-only access to the targeted configurable
61 target = property(
lambda x: x._target)
63 Read-only access to the ConfigClass
65 ConfigClass = property(
lambda x: x._ConfigClass)
68 Read-only access to the ConfigClass instance
70 value = property(
lambda x: x._value)
74 Call the confirurable.
75 With argument config=self.value along with any positional and kw args
80 Target a new configurable and ConfigClass
82 def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
83 if self._config._frozen:
84 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
87 ConfigClass = self._field.validateTarget(target,ConfigClass)
88 except BaseException, e:
89 raise FieldValidationError(self._field, self._config, e.message)
92 at = traceback.extract_stack()[:-1]
93 object.__setattr__(self,
"_target", target)
95 object.__setattr__(self,
"_ConfigClass",ConfigClass)
98 history = self._config._history.setdefault(self._field.name, [])
99 msg =
"retarget(target=%s, ConfigClass=%s)"%(
_typeStr(target),
_typeStr(ConfigClass))
100 history.append((msg, at, label))
103 return getattr(self._value, name)
108 Pretend to be an isntance of ConfigClass.
109 Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
111 if self._config._frozen:
112 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
114 if name
in self.__dict__:
116 object.__setattr__(self, name, value)
119 at = traceback.extract_stack()[:-1]
120 self._value.__setattr__(name, value, at=at, label=label)
124 Pretend to be an isntance of ConfigClass.
125 Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
127 if self._config._frozen:
128 raise FieldValidationError(self._field, self._config,
"Cannot modify a frozen Config")
132 object.__delattr__(self, name)
133 except AttributeError:
135 at = traceback.extract_stack()[:-1]
136 self._value.__delattr__(name, at=at, label=label)
141 A variant of a ConfigField which has a known configurable target
143 Behaves just like a ConfigField except that it can be 'retargeted' to point
144 at a different configurable. Further you can 'apply' to construct a fully
145 configured configurable.
151 if ConfigClass
is None:
153 ConfigClass=target.ConfigClass
155 raise AttributeError(
"'target' must define attribute 'ConfigClass'")
156 if not issubclass(ConfigClass, Config):
157 raise TypeError(
"'ConfigClass' is of incorrect type %s."\
158 "'ConfigClass' must be a subclass of Config"%
_typeStr(ConfigClass))
159 if not hasattr(target,
'__call__'):
160 raise ValueError (
"'target' must be callable")
161 if not hasattr(target,
'__module__')
or not hasattr(target,
'__name__'):
162 raise ValueError(
"'target' must be statically defined" \
163 "(must have '__module__' and '__name__' attributes)")
166 def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
168 @param target is the configurable target. Must be callable, and the first
169 parameter will be the value of this field
170 @param ConfigClass is the class of Config object expected by the target.
171 If not provided by target.ConfigClass it must be provided explicitly in this argument
177 if default != ConfigClass
and type(default) != ConfigClass:
178 raise TypeError(
"'default' is of incorrect type %s. Expected %s"%\
181 source = traceback.extract_stack(limit=2)[0]
182 self._setup(doc=doc, dtype=ConfigurableInstance, default=default, \
183 check=check, optional=
False, source=source)
188 value = instance._storage.get(self.name,
None)
191 at = traceback.extract_stack()[:-2]
193 instance._storage[self.name]=value
196 def __get__(self, instance, owner=None, at=None, label="default"):
197 if instance
is None or not isinstance(instance, Config):
200 return self.
__getOrMake(instance, at=at, label=label)
202 def __set__(self, instance, value, at=None, label="assignment"):
204 raise FieldValidationError(self, instance,
"Cannot modify a frozen Config")
206 at = traceback.extract_stack()[:-1]
209 if isinstance(value, ConfigurableInstance):
210 oldValue.retarget(value.target, value.ConfigClass, at, label)
211 oldValue.update(__at=at, __label=label, **value._storage)
212 elif type(value)==oldValue._ConfigClass:
213 oldValue.update(__at=at, __label=label, **value._storage)
214 elif value == oldValue.ConfigClass:
215 value = oldValue.ConfigClass()
216 oldValue.update(__at=at, __label=label, **value._storage)
218 msg =
"Value %s is of incorrect type %s. Expected %s" %\
220 raise FieldValidationError(self, instance, msg)
225 value._rename(fullname)
227 def save(self, outfile, instance):
235 ConfigClass = value.ConfigClass
236 for module
in set([target.__module__, ConfigClass.__module__]):
237 print >> outfile,
"import %s" % module
238 print >> outfile,
"%s.retarget(target=%s, ConfigClass=%s)"%\
249 return value.toDict()
255 if self.check
is not None and not self.check(value):
256 msg =
"%s is not a valid value"%str(value)
257 raise FieldValidationError(self, instance, msg)
260 """Customize deep-copying, because we always want a reference to the original typemap.
262 WARNING: this must be overridden by subclasses if they change the constructor signature!
264 return type(self)(doc=self.doc, target=self.
target, ConfigClass=self.
ConfigClass,
265 default=copy.deepcopy(self.default))
267 def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
268 """Helper function for Config.compare; used to compare two fields for equality.
270 @param[in] instance1 LHS Config instance to compare.
271 @param[in] instance2 RHS Config instance to compare.
272 @param[in] shortcut If True, return as soon as an inequality is found.
273 @param[in] rtol Relative tolerance for floating point comparisons.
274 @param[in] atol Absolute tolerance for floating point comparisons.
275 @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
276 to report inequalities.
278 Floating point comparisons are performed by numpy.allclose; refer to that for details.
280 c1 = getattr(instance1, self.name)._value
281 c2 = getattr(instance2, self.name)._value
286 return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)