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