LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
configurableField.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import traceback, copy
23 
24 from .config import Config, Field, _joinNamePath, _typeStr, FieldValidationError
25 from .comparison import compareConfigs, getComparisonName
26 
27 class ConfigurableInstance(object):
28  def __initValue(self, at, label):
29  """
30  if field.default is an instance of ConfigClass, custom construct
31  _value with the correct values from default.
32  otherwise call ConfigClass constructor
33  """
34  name=_joinNamePath(self._config._name, self._field.name)
35  if type(self._field.default)==self.ConfigClass:
36  storage = self._field.default._storage
37  else:
38  storage = {}
39  value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
40  object.__setattr__(self, "_value", value)
41 
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)
49 
50  if at is None:
51  at = traceback.extract_stack()[:-1]
52  at += [self._field.source]
53  self.__initValue(at, label)
54 
55  history = config._history.setdefault(field.name, [])
56  history.append(("Targeted and initialized from defaults", at, label))
57 
58  """
59  Read-only access to the targeted configurable
60  """
61  target = property(lambda x: x._target)
62  """
63  Read-only access to the ConfigClass
64  """
65  ConfigClass = property(lambda x: x._ConfigClass)
66 
67  """
68  Read-only access to the ConfigClass instance
69  """
70  value = property(lambda x: x._value)
71 
72  def apply(self, *args, **kw):
73  """
74  Call the confirurable.
75  With argument config=self.value along with any positional and kw args
76  """
77  return self.target(*args, config=self.value, **kw)
78 
79  """
80  Target a new configurable and ConfigClass
81  """
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")
85 
86  try:
87  ConfigClass = self._field.validateTarget(target,ConfigClass)
88  except BaseException, e:
89  raise FieldValidationError(self._field, self._config, e.message)
90 
91  if at is None:
92  at = traceback.extract_stack()[:-1]
93  object.__setattr__(self, "_target", target)
94  if ConfigClass != self.ConfigClass:
95  object.__setattr__(self, "_ConfigClass",ConfigClass)
96  self.__initValue(at, label)
97 
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))
101 
102  def __getattr__(self, name):
103  return getattr(self._value, name)
104 
105 
106  def __setattr__(self, name, value, at=None, label="assignment"):
107  """
108  Pretend to be an isntance of ConfigClass.
109  Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
110  """
111  if self._config._frozen:
112  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
113 
114  if name in self.__dict__:
115  #attribute exists in the ConfigurableInstance wrapper
116  object.__setattr__(self, name, value)
117  else:
118  if at is None:
119  at = traceback.extract_stack()[:-1]
120  self._value.__setattr__(name, value, at=at, label=label)
121 
122  def __delattr__(self, name, at=None, label="delete"):
123  """
124  Pretend to be an isntance of ConfigClass.
125  Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
126  """
127  if self._config._frozen:
128  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
129 
130  try:
131  #attribute exists in the ConfigurableInstance wrapper
132  object.__delattr__(self, name)
133  except AttributeError:
134  if at is None:
135  at = traceback.extract_stack()[:-1]
136  self._value.__delattr__(name, at=at, label=label)
137 
138 
139 class ConfigurableField(Field):
140  """
141  A variant of a ConfigField which has a known configurable target
142 
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.
146 
147 
148  """
149 
150  def validateTarget(self, target, ConfigClass):
151  if ConfigClass is None:
152  try:
153  ConfigClass=target.ConfigClass
154  except:
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)")
164  return ConfigClass
165 
166  def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
167  """
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
172  """
173  ConfigClass = self.validateTarget(target, ConfigClass)
174 
175  if default is None:
176  default=ConfigClass
177  if default != ConfigClass and type(default) != ConfigClass:
178  raise TypeError("'default' is of incorrect type %s. Expected %s"%\
179  (_typeStr(default), _typeStr(ConfigClass)))
180 
181  source = traceback.extract_stack(limit=2)[0]
182  self._setup(doc=doc, dtype=ConfigurableInstance, default=default, \
183  check=check, optional=False, source=source)
184  self.target = target
185  self.ConfigClass = ConfigClass
186 
187  def __getOrMake(self, instance, at=None, label="default"):
188  value = instance._storage.get(self.name, None)
189  if value is None:
190  if at is None:
191  at = traceback.extract_stack()[:-2]
192  value = ConfigurableInstance(instance, self, at=at, label=label)
193  instance._storage[self.name]=value
194  return value
195 
196  def __get__(self, instance, owner=None, at=None, label="default"):
197  if instance is None or not isinstance(instance, Config):
198  return self
199  else:
200  return self.__getOrMake(instance, at=at, label=label)
201 
202  def __set__(self, instance, value, at=None, label="assignment"):
203  if instance._frozen:
204  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
205  if at is None:
206  at = traceback.extract_stack()[:-1]
207  oldValue = self.__getOrMake(instance, at=at)
208 
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)
217  else:
218  msg = "Value %s is of incorrect type %s. Expected %s" %\
219  (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
220  raise FieldValidationError(self, instance, msg)
221 
222  def rename(self, instance):
223  fullname = _joinNamePath(instance._name, self.name)
224  value = self.__getOrMake(instance)
225  value._rename(fullname)
226 
227  def save(self, outfile, instance):
228  fullname = _joinNamePath(instance._name, self.name)
229  value = self.__getOrMake(instance)
230  target= value.target
231 
232  if target != self.target:
233  #not targeting the field-default target.
234  #save target information
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)"%\
239  (fullname, _typeStr(target), _typeStr(ConfigClass))
240  #save field values
241  value._save(outfile)
242 
243  def freeze(self, instance):
244  value = self.__getOrMake(instance)
245  value.freeze()
246 
247  def toDict(self, instance):
248  value = self.__get__(instance)
249  return value.toDict()
250 
251  def validate(self, instance):
252  value = self.__get__(instance)
253  value.validate()
254 
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)
258 
259  def __deepcopy__(self, memo):
260  """Customize deep-copying, because we always want a reference to the original typemap.
261 
262  WARNING: this must be overridden by subclasses if they change the constructor signature!
263  """
264  return type(self)(doc=self.doc, target=self.target, ConfigClass=self.ConfigClass,
265  default=copy.deepcopy(self.default))
266 
267  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
268  """Helper function for Config.compare; used to compare two fields for equality.
269 
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.
277 
278  Floating point comparisons are performed by numpy.allclose; refer to that for details.
279  """
280  c1 = getattr(instance1, self.name)._value
281  c2 = getattr(instance2, self.name)._value
282  name = getComparisonName(
283  _joinNamePath(instance1._name, self.name),
284  _joinNamePath(instance2._name, self.name)
285  )
286  return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)
287