lsst.pex.config  16.0-3-g9645794+5
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 
23 import copy
24 
25 from .config import Config, Field, _joinNamePath, _typeStr, FieldValidationError
26 from .comparison import compareConfigs, getComparisonName
27 from .callStack import getCallStack, getStackFrame
28 
29 
31  def __initValue(self, at, label):
32  """
33  if field.default is an instance of ConfigClass, custom construct
34  _value with the correct values from default.
35  otherwise call ConfigClass constructor
36  """
37  name = _joinNamePath(self._config._name, self._field.name)
38  if type(self._field.default) == self.ConfigClass:
39  storage = self._field.default._storage
40  else:
41  storage = {}
42  value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
43  object.__setattr__(self, "_value", value)
44 
45  def __init__(self, config, field, at=None, label="default"):
46  object.__setattr__(self, "_config", config)
47  object.__setattr__(self, "_field", field)
48  object.__setattr__(self, "__doc__", config)
49  object.__setattr__(self, "_target", field.target)
50  object.__setattr__(self, "_ConfigClass", field.ConfigClass)
51  object.__setattr__(self, "_value", None)
52 
53  if at is None:
54  at = getCallStack()
55  at += [self._field.source]
56  self.__initValue(at, label)
57 
58  history = config._history.setdefault(field.name, [])
59  history.append(("Targeted and initialized from defaults", at, label))
60 
61  """
62  Read-only access to the targeted configurable
63  """
64  target = property(lambda x: x._target)
65  """
66  Read-only access to the ConfigClass
67  """
68  ConfigClass = property(lambda x: x._ConfigClass)
69 
70  """
71  Read-only access to the ConfigClass instance
72  """
73  value = property(lambda x: x._value)
74 
75  def apply(self, *args, **kw):
76  """
77  Call the confirurable.
78  With argument config=self.value along with any positional and kw args
79  """
80  return self.target(*args, config=self.value, **kw)
81 
82  """
83  Target a new configurable and ConfigClass
84  """
85  def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
86  if self._config._frozen:
87  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
88 
89  try:
90  ConfigClass = self._field.validateTarget(target, ConfigClass)
91  except BaseException as e:
92  raise FieldValidationError(self._field, self._config, e.message)
93 
94  if at is None:
95  at = getCallStack()
96  object.__setattr__(self, "_target", target)
97  if ConfigClass != self.ConfigClass:
98  object.__setattr__(self, "_ConfigClass", ConfigClass)
99  self.__initValue(at, label)
100 
101  history = self._config._history.setdefault(self._field.name, [])
102  msg = "retarget(target=%s, ConfigClass=%s)" % (_typeStr(target), _typeStr(ConfigClass))
103  history.append((msg, at, label))
104 
105  def __getattr__(self, name):
106  return getattr(self._value, name)
107 
108  def __setattr__(self, name, value, at=None, label="assignment"):
109  """
110  Pretend to be an isntance of ConfigClass.
111  Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
112  """
113  if self._config._frozen:
114  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
115 
116  if name in self.__dict__:
117  # attribute exists in the ConfigurableInstance wrapper
118  object.__setattr__(self, name, value)
119  else:
120  if at is None:
121  at = getCallStack()
122  self._value.__setattr__(name, value, at=at, label=label)
123 
124  def __delattr__(self, name, at=None, label="delete"):
125  """
126  Pretend to be an isntance of ConfigClass.
127  Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
128  """
129  if self._config._frozen:
130  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
131 
132  try:
133  # attribute exists in the ConfigurableInstance wrapper
134  object.__delattr__(self, name)
135  except AttributeError:
136  if at is None:
137  at = getCallStack()
138  self._value.__delattr__(name, at=at, label=label)
139 
140 
142  """
143  A variant of a ConfigField which has a known configurable target
144 
145  Behaves just like a ConfigField except that it can be 'retargeted' to point
146  at a different configurable. Further you can 'apply' to construct a fully
147  configured configurable.
148 
149 
150  """
151 
152  def validateTarget(self, target, ConfigClass):
153  if ConfigClass is None:
154  try:
155  ConfigClass = target.ConfigClass
156  except Exception:
157  raise AttributeError("'target' must define attribute 'ConfigClass'")
158  if not issubclass(ConfigClass, Config):
159  raise TypeError("'ConfigClass' is of incorrect type %s."
160  "'ConfigClass' must be a subclass of Config" % _typeStr(ConfigClass))
161  if not hasattr(target, '__call__'):
162  raise ValueError("'target' must be callable")
163  if not hasattr(target, '__module__') or not hasattr(target, '__name__'):
164  raise ValueError("'target' must be statically defined"
165  "(must have '__module__' and '__name__' attributes)")
166  return ConfigClass
167 
168  def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
169  """
170  @param target is the configurable target. Must be callable, and the first
171  parameter will be the value of this field
172  @param ConfigClass is the class of Config object expected by the target.
173  If not provided by target.ConfigClass it must be provided explicitly in this argument
174  """
175  ConfigClass = self.validateTarget(target, ConfigClass)
176 
177  if default is None:
178  default = ConfigClass
179  if default != ConfigClass and type(default) != ConfigClass:
180  raise TypeError("'default' is of incorrect type %s. Expected %s" %
181  (_typeStr(default), _typeStr(ConfigClass)))
182 
183  source = getStackFrame()
184  self._setup(doc=doc, dtype=ConfigurableInstance, default=default,
185  check=check, optional=False, source=source)
186  self.target = target
187  self.ConfigClass = ConfigClass
188 
189  def __getOrMake(self, instance, at=None, label="default"):
190  value = instance._storage.get(self.name, None)
191  if value is None:
192  if at is None:
193  at = getCallStack(1)
194  value = ConfigurableInstance(instance, self, at=at, label=label)
195  instance._storage[self.name] = value
196  return value
197 
198  def __get__(self, instance, owner=None, at=None, label="default"):
199  if instance is None or not isinstance(instance, Config):
200  return self
201  else:
202  return self.__getOrMake(instance, at=at, label=label)
203 
204  def __set__(self, instance, value, at=None, label="assignment"):
205  if instance._frozen:
206  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
207  if at is None:
208  at = getCallStack()
209  oldValue = self.__getOrMake(instance, at=at)
210 
211  if isinstance(value, ConfigurableInstance):
212  oldValue.retarget(value.target, value.ConfigClass, at, label)
213  oldValue.update(__at=at, __label=label, **value._storage)
214  elif type(value) == oldValue._ConfigClass:
215  oldValue.update(__at=at, __label=label, **value._storage)
216  elif value == oldValue.ConfigClass:
217  value = oldValue.ConfigClass()
218  oldValue.update(__at=at, __label=label, **value._storage)
219  else:
220  msg = "Value %s is of incorrect type %s. Expected %s" % \
221  (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
222  raise FieldValidationError(self, instance, msg)
223 
224  def rename(self, instance):
225  fullname = _joinNamePath(instance._name, self.name)
226  value = self.__getOrMake(instance)
227  value._rename(fullname)
228 
229  def save(self, outfile, instance):
230  fullname = _joinNamePath(instance._name, self.name)
231  value = self.__getOrMake(instance)
232  target = value.target
233 
234  if target != self.target:
235  # not targeting the field-default target.
236  # save target information
237  ConfigClass = value.ConfigClass
238  for module in set([target.__module__, ConfigClass.__module__]):
239  outfile.write(u"import {}\n".format(module))
240  outfile.write(u"{}.retarget(target={}, ConfigClass={})\n\n".format(fullname,
241  _typeStr(target),
242  _typeStr(ConfigClass)))
243  # save field values
244  value._save(outfile)
245 
246  def freeze(self, instance):
247  value = self.__getOrMake(instance)
248  value.freeze()
249 
250  def toDict(self, instance):
251  value = self.__get__(instance)
252  return value.toDict()
253 
254  def validate(self, instance):
255  value = self.__get__(instance)
256  value.validate()
257 
258  if self.check is not None and not self.check(value):
259  msg = "%s is not a valid value" % str(value)
260  raise FieldValidationError(self, instance, msg)
261 
262  def __deepcopy__(self, memo):
263  """Customize deep-copying, because we always want a reference to the original typemap.
264 
265  WARNING: this must be overridden by subclasses if they change the constructor signature!
266  """
267  return type(self)(doc=self.doc, target=self.target, ConfigClass=self.ConfigClass,
268  default=copy.deepcopy(self.default))
269 
270  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
271  """Helper function for Config.compare; used to compare two fields for equality.
272 
273  @param[in] instance1 LHS Config instance to compare.
274  @param[in] instance2 RHS Config instance to compare.
275  @param[in] shortcut If True, return as soon as an inequality is found.
276  @param[in] rtol Relative tolerance for floating point comparisons.
277  @param[in] atol Absolute tolerance for floating point comparisons.
278  @param[in] output If not None, a callable that takes a string, used (possibly repeatedly)
279  to report inequalities.
280 
281  Floating point comparisons are performed by numpy.allclose; refer to that for details.
282  """
283  c1 = getattr(instance1, self.name)._value
284  c2 = getattr(instance2, self.name)._value
285  name = getComparisonName(
286  _joinNamePath(instance1._name, self.name),
287  _joinNamePath(instance2._name, self.name)
288  )
289  return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)
def __setattr__(self, name, value, at=None, label="assignment")
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:67
def __init__(self, doc, target, ConfigClass=None, default=None, check=None)
def getCallStack(skip=0)
Definition: callStack.py:153
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:272
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:173
def getStackFrame(relative=0)
Definition: callStack.py:50
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:129
def retarget(self, target, ConfigClass=None, at=None, label="retarget")
def __init__(self, config, field, at=None, label="default")
def __getOrMake(self, instance, at=None, label="default")
def __delattr__(self, name, at=None, label="delete")
def __get__(self, instance, owner=None, at=None, label="default")
def __set__(self, instance, value, at=None, label="assignment")
def getComparisonName(name1, name2)
Definition: comparison.py:35