LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
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 __all__ = ('ConfigurableInstance', 'ConfigurableField')
23 
24 import copy
25 
26 from .config import Config, Field, _joinNamePath, _typeStr, FieldValidationError
27 from .comparison import compareConfigs, getComparisonName
28 from .callStack import getCallStack, getStackFrame
29 
30 
32  """A retargetable configuration in a `ConfigurableField` that proxies
33  a `~lsst.pex.config.Config`.
34 
35  Notes
36  -----
37  ``ConfigurableInstance`` implements ``__getattr__`` and ``__setattr__``
38  methods that forward to the `~lsst.pex.config.Config` it holds.
39  ``ConfigurableInstance`` adds a `retarget` method.
40 
41  The actual `~lsst.pex.config.Config` instance is accessed using the
42  ``value`` property (e.g. to get its documentation). The associated
43  configurable object (usually a `~lsst.pipe.base.Task`) is accessed
44  using the ``target`` property.
45  """
46 
47  def __initValue(self, at, label):
48  """Construct value of field.
49 
50  Notes
51  -----
52  If field.default is an instance of `lsst.pex.config.ConfigClass`,
53  custom construct ``_value`` with the correct values from default.
54  Otherwise, call ``ConfigClass`` constructor
55  """
56  name = _joinNamePath(self._config._name, self._field.name)
57  if type(self._field.default) == self.ConfigClass:
58  storage = self._field.default._storage
59  else:
60  storage = {}
61  value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
62  object.__setattr__(self, "_value", value)
63 
64  def __init__(self, config, field, at=None, label="default"):
65  object.__setattr__(self, "_config", config)
66  object.__setattr__(self, "_field", field)
67  object.__setattr__(self, "__doc__", config)
68  object.__setattr__(self, "_target", field.target)
69  object.__setattr__(self, "_ConfigClass", field.ConfigClass)
70  object.__setattr__(self, "_value", None)
71 
72  if at is None:
73  at = getCallStack()
74  at += [self._field.source]
75  self.__initValue(at, label)
76 
77  history = config._history.setdefault(field.name, [])
78  history.append(("Targeted and initialized from defaults", at, label))
79 
80  target = property(lambda x: x._target)
81  """The targeted configurable (read-only).
82  """
83 
84  ConfigClass = property(lambda x: x._ConfigClass)
85  """The configuration class (read-only)
86  """
87 
88  value = property(lambda x: x._value)
89  """The `ConfigClass` instance (`lsst.pex.config.ConfigClass`-type,
90  read-only).
91  """
92 
93  def apply(self, *args, **kw):
94  """Call the configurable.
95 
96  Notes
97  -----
98  In addition to the user-provided positional and keyword arguments,
99  the configurable is also provided a keyword argument ``config`` with
100  the value of `ConfigurableInstance.value`.
101  """
102  return self.target(*args, config=self.value, **kw)
103 
104  def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
105  """Target a new configurable and ConfigClass
106  """
107  if self._config._frozen:
108  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
109 
110  try:
111  ConfigClass = self._field.validateTarget(target, ConfigClass)
112  except BaseException as e:
113  raise FieldValidationError(self._field, self._config, e.message)
114 
115  if at is None:
116  at = getCallStack()
117  object.__setattr__(self, "_target", target)
118  if ConfigClass != self.ConfigClass:
119  object.__setattr__(self, "_ConfigClass", ConfigClass)
120  self.__initValue(at, label)
121 
122  history = self._config._history.setdefault(self._field.name, [])
123  msg = "retarget(target=%s, ConfigClass=%s)" % (_typeStr(target), _typeStr(ConfigClass))
124  history.append((msg, at, label))
125 
126  def __getattr__(self, name):
127  return getattr(self._value, name)
128 
129  def __setattr__(self, name, value, at=None, label="assignment"):
130  """Pretend to be an instance of ConfigClass.
131 
132  Attributes defined by ConfigurableInstance will shadow those defined in ConfigClass
133  """
134  if self._config._frozen:
135  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
136 
137  if name in self.__dict__:
138  # attribute exists in the ConfigurableInstance wrapper
139  object.__setattr__(self, name, value)
140  else:
141  if at is None:
142  at = getCallStack()
143  self._value.__setattr__(name, value, at=at, label=label)
144 
145  def __delattr__(self, name, at=None, label="delete"):
146  """
147  Pretend to be an isntance of ConfigClass.
148  Attributes defiend by ConfigurableInstance will shadow those defined in ConfigClass
149  """
150  if self._config._frozen:
151  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
152 
153  try:
154  # attribute exists in the ConfigurableInstance wrapper
155  object.__delattr__(self, name)
156  except AttributeError:
157  if at is None:
158  at = getCallStack()
159  self._value.__delattr__(name, at=at, label=label)
160 
161 
163  """A configuration field (`~lsst.pex.config.Field` subclass) that can be
164  can be retargeted towards a different configurable (often a
165  `lsst.pipe.base.Task` subclass).
166 
167  The ``ConfigurableField`` is often used to configure subtasks, which are
168  tasks (`~lsst.pipe.base.Task`) called by a parent task.
169 
170  Parameters
171  ----------
172  doc : `str`
173  A description of the configuration field.
174  target : configurable class
175  The configurable target. Configurables have a ``ConfigClass``
176  attribute. Within the task framework, configurables are
177  `lsst.pipe.base.Task` subclasses)
178  ConfigClass : `lsst.pex.config.Config`-type, optional
179  The subclass of `lsst.pex.config.Config` expected as the configuration
180  class of the ``target``. If ``ConfigClass`` is unset then
181  ``target.ConfigClass`` is used.
182  default : ``ConfigClass``-type, optional
183  The default configuration class. Normally this parameter is not set,
184  and defaults to ``ConfigClass`` (or ``target.ConfigClass``).
185  check : callable, optional
186  Callable that takes the field's value (the ``target``) as its only
187  positional argument, and returns `True` if the ``target`` is valid (and
188  `False` otherwise).
189 
190  See also
191  --------
192  ChoiceField
193  ConfigChoiceField
194  ConfigDictField
195  ConfigField
196  DictField
197  Field
198  ListField
199  RangeField
200  RegistryField
201 
202  Notes
203  -----
204  You can use the `ConfigurableInstance.apply` method to construct a
205  fully-configured configurable.
206  """
207 
208  def validateTarget(self, target, ConfigClass):
209  """Validate the target and configuration class.
210 
211  Parameters
212  ----------
213  target
214  The configurable being verified.
215  ConfigClass : `lsst.pex.config.Config`-type or `None`
216  The configuration class associated with the ``target``. This can
217  be `None` if ``target`` has a ``ConfigClass`` attribute.
218 
219  Raises
220  ------
221  AttributeError
222  Raised if ``ConfigClass`` is `None` and ``target`` does not have a
223  ``ConfigClass`` attribute.
224  TypeError
225  Raised if ``ConfigClass`` is not a `~lsst.pex.config.Config`
226  subclass.
227  ValueError
228  Raised if:
229 
230  - ``target`` is not callable (callables have a ``__call__``
231  method).
232  - ``target`` is not startically defined (does not have
233  ``__module__`` or ``__name__`` attributes).
234  """
235  if ConfigClass is None:
236  try:
237  ConfigClass = target.ConfigClass
238  except Exception:
239  raise AttributeError("'target' must define attribute 'ConfigClass'")
240  if not issubclass(ConfigClass, Config):
241  raise TypeError("'ConfigClass' is of incorrect type %s."
242  "'ConfigClass' must be a subclass of Config" % _typeStr(ConfigClass))
243  if not hasattr(target, '__call__'):
244  raise ValueError("'target' must be callable")
245  if not hasattr(target, '__module__') or not hasattr(target, '__name__'):
246  raise ValueError("'target' must be statically defined"
247  "(must have '__module__' and '__name__' attributes)")
248  return ConfigClass
249 
250  def __init__(self, doc, target, ConfigClass=None, default=None, check=None):
251  ConfigClass = self.validateTarget(target, ConfigClass)
252 
253  if default is None:
254  default = ConfigClass
255  if default != ConfigClass and type(default) != ConfigClass:
256  raise TypeError("'default' is of incorrect type %s. Expected %s" %
257  (_typeStr(default), _typeStr(ConfigClass)))
258 
259  source = getStackFrame()
260  self._setup(doc=doc, dtype=ConfigurableInstance, default=default,
261  check=check, optional=False, source=source)
262  self.target = target
263  self.ConfigClass = ConfigClass
264 
265  def __getOrMake(self, instance, at=None, label="default"):
266  value = instance._storage.get(self.name, None)
267  if value is None:
268  if at is None:
269  at = getCallStack(1)
270  value = ConfigurableInstance(instance, self, at=at, label=label)
271  instance._storage[self.name] = value
272  return value
273 
274  def __get__(self, instance, owner=None, at=None, label="default"):
275  if instance is None or not isinstance(instance, Config):
276  return self
277  else:
278  return self.__getOrMake(instance, at=at, label=label)
279 
280  def __set__(self, instance, value, at=None, label="assignment"):
281  if instance._frozen:
282  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
283  if at is None:
284  at = getCallStack()
285  oldValue = self.__getOrMake(instance, at=at)
286 
287  if isinstance(value, ConfigurableInstance):
288  oldValue.retarget(value.target, value.ConfigClass, at, label)
289  oldValue.update(__at=at, __label=label, **value._storage)
290  elif type(value) == oldValue._ConfigClass:
291  oldValue.update(__at=at, __label=label, **value._storage)
292  elif value == oldValue.ConfigClass:
293  value = oldValue.ConfigClass()
294  oldValue.update(__at=at, __label=label, **value._storage)
295  else:
296  msg = "Value %s is of incorrect type %s. Expected %s" % \
297  (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
298  raise FieldValidationError(self, instance, msg)
299 
300  def rename(self, instance):
301  fullname = _joinNamePath(instance._name, self.name)
302  value = self.__getOrMake(instance)
303  value._rename(fullname)
304 
305  def _collectImports(self, instance, imports):
306  value = self.__get__(instance)
307  target = value.target
308  imports.add(target.__module__)
309  value.value._collectImports()
310  imports |= value.value._imports
311 
312  def save(self, outfile, instance):
313  fullname = _joinNamePath(instance._name, self.name)
314  value = self.__getOrMake(instance)
315  target = value.target
316 
317  if target != self.target:
318  # not targeting the field-default target.
319  # save target information
320  ConfigClass = value.ConfigClass
321  outfile.write(u"{}.retarget(target={}, ConfigClass={})\n\n".format(fullname,
322  _typeStr(target),
323  _typeStr(ConfigClass)))
324  # save field values
325  value._save(outfile)
326 
327  def freeze(self, instance):
328  value = self.__getOrMake(instance)
329  value.freeze()
330 
331  def toDict(self, instance):
332  value = self.__get__(instance)
333  return value.toDict()
334 
335  def validate(self, instance):
336  value = self.__get__(instance)
337  value.validate()
338 
339  if self.check is not None and not self.check(value):
340  msg = "%s is not a valid value" % str(value)
341  raise FieldValidationError(self, instance, msg)
342 
343  def __deepcopy__(self, memo):
344  """Customize deep-copying, because we always want a reference to the
345  original typemap.
346 
347  WARNING: this must be overridden by subclasses if they change the
348  constructor signature!
349  """
350  return type(self)(doc=self.doc, target=self.target, ConfigClass=self.ConfigClass,
351  default=copy.deepcopy(self.default))
352 
353  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
354  """Compare two fields for equality.
355 
356  Used by `lsst.pex.ConfigDictField.compare`.
357 
358  Parameters
359  ----------
360  instance1 : `lsst.pex.config.Config`
361  Left-hand side config instance to compare.
362  instance2 : `lsst.pex.config.Config`
363  Right-hand side config instance to compare.
364  shortcut : `bool`
365  If `True`, this function returns as soon as an inequality if found.
366  rtol : `float`
367  Relative tolerance for floating point comparisons.
368  atol : `float`
369  Absolute tolerance for floating point comparisons.
370  output : callable
371  A callable that takes a string, used (possibly repeatedly) to
372  report inequalities. For example: `print`.
373 
374  Returns
375  -------
376  isEqual : bool
377  `True` if the fields are equal, `False` otherwise.
378 
379  Notes
380  -----
381  Floating point comparisons are performed by `numpy.allclose`.
382  """
383  c1 = getattr(instance1, self.name)._value
384  c2 = getattr(instance2, self.name)._value
385  name = getComparisonName(
386  _joinNamePath(instance1._name, self.name),
387  _joinNamePath(instance2._name, self.name)
388  )
389  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:105
def __init__(self, doc, target, ConfigClass=None, default=None, check=None)
def getCallStack(skip=0)
Definition: callStack.py:169
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:476
def _setup(self, doc, dtype, default, check, optional, source)
Definition: config.py:273
def getStackFrame(relative=0)
Definition: callStack.py:52
table::Key< int > type
Definition: Detector.cc:167
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
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:34