LSSTApplications  19.0.0-14-gb0260a2+72efe9b372,20.0.0+7927753e06,20.0.0+8829bf0056,20.0.0+995114c5d2,20.0.0+b6f4b2abd1,20.0.0+bddc4f4cbe,20.0.0-1-g253301a+8829bf0056,20.0.0-1-g2b7511a+0d71a2d77f,20.0.0-1-g5b95a8c+7461dd0434,20.0.0-12-g321c96ea+23efe4bbff,20.0.0-16-gfab17e72e+fdf35455f6,20.0.0-2-g0070d88+ba3ffc8f0b,20.0.0-2-g4dae9ad+ee58a624b3,20.0.0-2-g61b8584+5d3db074ba,20.0.0-2-gb780d76+d529cf1a41,20.0.0-2-ged6426c+226a441f5f,20.0.0-2-gf072044+8829bf0056,20.0.0-2-gf1f7952+ee58a624b3,20.0.0-20-geae50cf+e37fec0aee,20.0.0-25-g3dcad98+544a109665,20.0.0-25-g5eafb0f+ee58a624b3,20.0.0-27-g64178ef+f1f297b00a,20.0.0-3-g4cc78c6+e0676b0dc8,20.0.0-3-g8f21e14+4fd2c12c9a,20.0.0-3-gbd60e8c+187b78b4b8,20.0.0-3-gbecbe05+48431fa087,20.0.0-38-ge4adf513+a12e1f8e37,20.0.0-4-g97dc21a+544a109665,20.0.0-4-gb4befbc+087873070b,20.0.0-4-gf910f65+5d3db074ba,20.0.0-5-gdfe0fee+199202a608,20.0.0-5-gfbfe500+d529cf1a41,20.0.0-6-g64f541c+d529cf1a41,20.0.0-6-g9a5b7a1+a1cd37312e,20.0.0-68-ga3f3dda+5fca18c6a4,20.0.0-9-g4aef684+e18322736b,w.2020.45
LSSTDataManagementBasePackage
configurableField.py
Go to the documentation of this file.
1 # This file is part of pex_config.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (http://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This software is dual licensed under the GNU General Public License and also
10 # under a 3-clause BSD license. Recipients may choose which of these licenses
11 # to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12 # respectively. If you choose the GPL option then the following text applies
13 # (but note that there is still no warranty even if you opt for BSD instead):
14 #
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 
28 __all__ = ('ConfigurableInstance', 'ConfigurableField')
29 
30 import copy
31 
32 from .config import Config, Field, _joinNamePath, _typeStr, FieldValidationError
33 from .comparison import compareConfigs, getComparisonName
34 from .callStack import getCallStack, getStackFrame
35 
36 
38  """A retargetable configuration in a `ConfigurableField` that proxies
39  a `~lsst.pex.config.Config`.
40 
41  Notes
42  -----
43  ``ConfigurableInstance`` implements ``__getattr__`` and ``__setattr__``
44  methods that forward to the `~lsst.pex.config.Config` it holds.
45  ``ConfigurableInstance`` adds a `retarget` method.
46 
47  The actual `~lsst.pex.config.Config` instance is accessed using the
48  ``value`` property (e.g. to get its documentation). The associated
49  configurable object (usually a `~lsst.pipe.base.Task`) is accessed
50  using the ``target`` property.
51  """
52 
53  def __initValue(self, at, label):
54  """Construct value of field.
55 
56  Notes
57  -----
58  If field.default is an instance of `lsst.pex.config.ConfigClass`,
59  custom construct ``_value`` with the correct values from default.
60  Otherwise, call ``ConfigClass`` constructor
61  """
62  name = _joinNamePath(self._config._name, self._field.name)
63  if type(self._field.default) == self.ConfigClass:
64  storage = self._field.default._storage
65  else:
66  storage = {}
67  value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
68  object.__setattr__(self, "_value", value)
69 
70  def __init__(self, config, field, at=None, label="default"):
71  object.__setattr__(self, "_config", config)
72  object.__setattr__(self, "_field", field)
73  object.__setattr__(self, "__doc__", config)
74  object.__setattr__(self, "_target", field.target)
75  object.__setattr__(self, "_ConfigClass", field.ConfigClass)
76  object.__setattr__(self, "_value", None)
77 
78  if at is None:
79  at = getCallStack()
80  at += [self._field.source]
81  self.__initValue(at, label)
82 
83  history = config._history.setdefault(field.name, [])
84  history.append(("Targeted and initialized from defaults", at, label))
85 
86  target = property(lambda x: x._target)
87  """The targeted configurable (read-only).
88  """
89 
90  ConfigClass = property(lambda x: x._ConfigClass)
91  """The configuration class (read-only)
92  """
93 
94  value = property(lambda x: x._value)
95  """The `ConfigClass` instance (`lsst.pex.config.ConfigClass`-type,
96  read-only).
97  """
98 
99  def apply(self, *args, **kw):
100  """Call the configurable.
101 
102  Notes
103  -----
104  In addition to the user-provided positional and keyword arguments,
105  the configurable is also provided a keyword argument ``config`` with
106  the value of `ConfigurableInstance.value`.
107  """
108  return self.target(*args, config=self.value, **kw)
109 
110  def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
111  """Target a new configurable and ConfigClass
112  """
113  if self._config._frozen:
114  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
115 
116  try:
117  ConfigClass = self._field.validateTarget(target, ConfigClass)
118  except BaseException as e:
119  raise FieldValidationError(self._field, self._config, e.message)
120 
121  if at is None:
122  at = getCallStack()
123  object.__setattr__(self, "_target", target)
124  if ConfigClass != self.ConfigClass:
125  object.__setattr__(self, "_ConfigClass", ConfigClass)
126  self.__initValue(at, label)
127 
128  history = self._config._history.setdefault(self._field.name, [])
129  msg = "retarget(target=%s, ConfigClass=%s)" % (_typeStr(target), _typeStr(ConfigClass))
130  history.append((msg, at, label))
131 
132  def __getattr__(self, name):
133  return getattr(self._value, name)
134 
135  def __setattr__(self, name, value, at=None, label="assignment"):
136  """Pretend to be an instance of ConfigClass.
137 
138  Attributes defined by ConfigurableInstance will shadow those defined
139  in ConfigClass
140  """
141  if self._config._frozen:
142  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
143 
144  if name in self.__dict__:
145  # attribute exists in the ConfigurableInstance wrapper
146  object.__setattr__(self, name, value)
147  else:
148  if at is None:
149  at = getCallStack()
150  self._value.__setattr__(name, value, at=at, label=label)
151 
152  def __delattr__(self, name, at=None, label="delete"):
153  """
154  Pretend to be an isntance of ConfigClass.
155  Attributes defiend by ConfigurableInstance will shadow those defined
156  in ConfigClass
157  """
158  if self._config._frozen:
159  raise FieldValidationError(self._field, self._config, "Cannot modify a frozen Config")
160 
161  try:
162  # attribute exists in the ConfigurableInstance wrapper
163  object.__delattr__(self, name)
164  except AttributeError:
165  if at is None:
166  at = getCallStack()
167  self._value.__delattr__(name, at=at, label=label)
168 
169 
171  """A configuration field (`~lsst.pex.config.Field` subclass) that can be
172  can be retargeted towards a different configurable (often a
173  `lsst.pipe.base.Task` subclass).
174 
175  The ``ConfigurableField`` is often used to configure subtasks, which are
176  tasks (`~lsst.pipe.base.Task`) called by a parent task.
177 
178  Parameters
179  ----------
180  doc : `str`
181  A description of the configuration field.
182  target : configurable class
183  The configurable target. Configurables have a ``ConfigClass``
184  attribute. Within the task framework, configurables are
185  `lsst.pipe.base.Task` subclasses)
186  ConfigClass : `lsst.pex.config.Config`-type, optional
187  The subclass of `lsst.pex.config.Config` expected as the configuration
188  class of the ``target``. If ``ConfigClass`` is unset then
189  ``target.ConfigClass`` is used.
190  default : ``ConfigClass``-type, optional
191  The default configuration class. Normally this parameter is not set,
192  and defaults to ``ConfigClass`` (or ``target.ConfigClass``).
193  check : callable, optional
194  Callable that takes the field's value (the ``target``) as its only
195  positional argument, and returns `True` if the ``target`` is valid (and
196  `False` otherwise).
197  deprecated : None or `str`, optional
198  A description of why this Field is deprecated, including removal date.
199  If not None, the string is appended to the docstring for this Field.
200 
201  See also
202  --------
203  ChoiceField
204  ConfigChoiceField
205  ConfigDictField
206  ConfigField
207  DictField
208  Field
209  ListField
210  RangeField
211  RegistryField
212 
213  Notes
214  -----
215  You can use the `ConfigurableInstance.apply` method to construct a
216  fully-configured configurable.
217  """
218 
219  def validateTarget(self, target, ConfigClass):
220  """Validate the target and configuration class.
221 
222  Parameters
223  ----------
224  target
225  The configurable being verified.
226  ConfigClass : `lsst.pex.config.Config`-type or `None`
227  The configuration class associated with the ``target``. This can
228  be `None` if ``target`` has a ``ConfigClass`` attribute.
229 
230  Raises
231  ------
232  AttributeError
233  Raised if ``ConfigClass`` is `None` and ``target`` does not have a
234  ``ConfigClass`` attribute.
235  TypeError
236  Raised if ``ConfigClass`` is not a `~lsst.pex.config.Config`
237  subclass.
238  ValueError
239  Raised if:
240 
241  - ``target`` is not callable (callables have a ``__call__``
242  method).
243  - ``target`` is not startically defined (does not have
244  ``__module__`` or ``__name__`` attributes).
245  """
246  if ConfigClass is None:
247  try:
248  ConfigClass = target.ConfigClass
249  except Exception:
250  raise AttributeError("'target' must define attribute 'ConfigClass'")
251  if not issubclass(ConfigClass, Config):
252  raise TypeError("'ConfigClass' is of incorrect type %s."
253  "'ConfigClass' must be a subclass of Config" % _typeStr(ConfigClass))
254  if not hasattr(target, '__call__'):
255  raise ValueError("'target' must be callable")
256  if not hasattr(target, '__module__') or not hasattr(target, '__name__'):
257  raise ValueError("'target' must be statically defined"
258  "(must have '__module__' and '__name__' attributes)")
259  return ConfigClass
260 
261  def __init__(self, doc, target, ConfigClass=None, default=None, check=None, deprecated=None):
262  ConfigClass = self.validateTarget(target, ConfigClass)
263 
264  if default is None:
265  default = ConfigClass
266  if default != ConfigClass and type(default) != ConfigClass:
267  raise TypeError("'default' is of incorrect type %s. Expected %s" %
268  (_typeStr(default), _typeStr(ConfigClass)))
269 
270  source = getStackFrame()
271  self._setup(doc=doc, dtype=ConfigurableInstance, default=default,
272  check=check, optional=False, source=source, deprecated=deprecated)
273  self.target = target
274  self.ConfigClass = ConfigClass
275 
276  def __getOrMake(self, instance, at=None, label="default"):
277  value = instance._storage.get(self.name, None)
278  if value is None:
279  if at is None:
280  at = getCallStack(1)
281  value = ConfigurableInstance(instance, self, at=at, label=label)
282  instance._storage[self.name] = value
283  return value
284 
285  def __get__(self, instance, owner=None, at=None, label="default"):
286  if instance is None or not isinstance(instance, Config):
287  return self
288  else:
289  return self.__getOrMake(instance, at=at, label=label)
290 
291  def __set__(self, instance, value, at=None, label="assignment"):
292  if instance._frozen:
293  raise FieldValidationError(self, instance, "Cannot modify a frozen Config")
294  if at is None:
295  at = getCallStack()
296  oldValue = self.__getOrMake(instance, at=at)
297 
298  if isinstance(value, ConfigurableInstance):
299  oldValue.retarget(value.target, value.ConfigClass, at, label)
300  oldValue.update(__at=at, __label=label, **value._storage)
301  elif type(value) == oldValue._ConfigClass:
302  oldValue.update(__at=at, __label=label, **value._storage)
303  elif value == oldValue.ConfigClass:
304  value = oldValue.ConfigClass()
305  oldValue.update(__at=at, __label=label, **value._storage)
306  else:
307  msg = "Value %s is of incorrect type %s. Expected %s" % \
308  (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
309  raise FieldValidationError(self, instance, msg)
310 
311  def rename(self, instance):
312  fullname = _joinNamePath(instance._name, self.name)
313  value = self.__getOrMake(instance)
314  value._rename(fullname)
315 
316  def _collectImports(self, instance, imports):
317  value = self.__get__(instance)
318  target = value.target
319  imports.add(target.__module__)
320  value.value._collectImports()
321  imports |= value.value._imports
322 
323  def save(self, outfile, instance):
324  fullname = _joinNamePath(instance._name, self.name)
325  value = self.__getOrMake(instance)
326  target = value.target
327 
328  if target != self.target:
329  # not targeting the field-default target.
330  # save target information
331  ConfigClass = value.ConfigClass
332  outfile.write(u"{}.retarget(target={}, ConfigClass={})\n\n".format(fullname,
333  _typeStr(target),
334  _typeStr(ConfigClass)))
335  # save field values
336  value._save(outfile)
337 
338  def freeze(self, instance):
339  value = self.__getOrMake(instance)
340  value.freeze()
341 
342  def toDict(self, instance):
343  value = self.__get__(instance)
344  return value.toDict()
345 
346  def validate(self, instance):
347  value = self.__get__(instance)
348  value.validate()
349 
350  if self.check is not None and not self.check(value):
351  msg = "%s is not a valid value" % str(value)
352  raise FieldValidationError(self, instance, msg)
353 
354  def __deepcopy__(self, memo):
355  """Customize deep-copying, because we always want a reference to the
356  original typemap.
357 
358  WARNING: this must be overridden by subclasses if they change the
359  constructor signature!
360  """
361  return type(self)(doc=self.doc, target=self.target, ConfigClass=self.ConfigClass,
362  default=copy.deepcopy(self.default))
363 
364  def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
365  """Compare two fields for equality.
366 
367  Used by `lsst.pex.ConfigDictField.compare`.
368 
369  Parameters
370  ----------
371  instance1 : `lsst.pex.config.Config`
372  Left-hand side config instance to compare.
373  instance2 : `lsst.pex.config.Config`
374  Right-hand side config instance to compare.
375  shortcut : `bool`
376  If `True`, this function returns as soon as an inequality if found.
377  rtol : `float`
378  Relative tolerance for floating point comparisons.
379  atol : `float`
380  Absolute tolerance for floating point comparisons.
381  output : callable
382  A callable that takes a string, used (possibly repeatedly) to
383  report inequalities. For example: `print`.
384 
385  Returns
386  -------
387  isEqual : bool
388  `True` if the fields are equal, `False` otherwise.
389 
390  Notes
391  -----
392  Floating point comparisons are performed by `numpy.allclose`.
393  """
394  c1 = getattr(instance1, self.name)._value
395  c2 = getattr(instance2, self.name)._value
396  name = getComparisonName(
397  _joinNamePath(instance1._name, self.name),
398  _joinNamePath(instance2._name, self.name)
399  )
400  return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)
lsst.pex.config.configurableField.ConfigurableInstance.__setattr__
def __setattr__(self, name, value, at=None, label="assignment")
Definition: configurableField.py:135
lsst.pex.config.configurableField.ConfigurableInstance.__initValue
def __initValue(self, at, label)
Definition: configurableField.py:53
lsst.pex.config.configurableField.ConfigurableField.__get__
def __get__(self, instance, owner=None, at=None, label="default")
Definition: configurableField.py:285
lsst.pex.config.configurableField.ConfigurableField.validateTarget
def validateTarget(self, target, ConfigClass)
Definition: configurableField.py:219
lsst.pex.config.configurableField.ConfigurableInstance.__getattr__
def __getattr__(self, name)
Definition: configurableField.py:132
lsst.pex.config.configurableField.ConfigurableInstance.retarget
def retarget(self, target, ConfigClass=None, at=None, label="retarget")
Definition: configurableField.py:110
lsst.pex.config.configurableField.ConfigurableField.__deepcopy__
def __deepcopy__(self, memo)
Definition: configurableField.py:354
lsst.pex.config.configurableField.ConfigurableField
Definition: configurableField.py:170
lsst.pex.config.configurableField.ConfigurableInstance.__delattr__
def __delattr__(self, name, at=None, label="delete")
Definition: configurableField.py:152
lsst.pex.config.configurableField.ConfigurableField.freeze
def freeze(self, instance)
Definition: configurableField.py:338
lsst.pex.config.configurableField.ConfigurableInstance.ConfigClass
ConfigClass
Definition: configurableField.py:90
lsst.pex.config.configurableField.ConfigurableField.target
target
Definition: configurableField.py:273
lsst.pex.config.history.format
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
lsst.pex.config.configurableField.ConfigurableField.validate
def validate(self, instance)
Definition: configurableField.py:346
lsst.pex.config.config.Field.default
default
Definition: config.py:359
lsst.pex.config.configurableField.ConfigurableInstance
Definition: configurableField.py:37
lsst.pex.config.callStack.getCallStack
def getCallStack(skip=0)
Definition: callStack.py:175
lsst.pex.config.configurableField.ConfigurableField.__init__
def __init__(self, doc, target, ConfigClass=None, default=None, check=None, deprecated=None)
Definition: configurableField.py:261
lsst.pex.config.configurableField.ConfigurableField.toDict
def toDict(self, instance)
Definition: configurableField.py:342
lsst.pex.config.config.FieldValidationError
Definition: config.py:196
lsst.pex.config.configurableField.ConfigurableInstance.apply
def apply(self, *args, **kw)
Definition: configurableField.py:99
lsst.pex.config.configurableField.ConfigurableField.rename
def rename(self, instance)
Definition: configurableField.py:311
lsst.pex.config.configurableField.ConfigurableField.__getOrMake
def __getOrMake(self, instance, at=None, label="default")
Definition: configurableField.py:276
lsst.pex.config.config.Field.check
check
Definition: config.py:363
lsst.pex.config.config.Field.doc
doc
Definition: config.py:346
lsst.pex.config.configurableField.ConfigurableField.__set__
def __set__(self, instance, value, at=None, label="assignment")
Definition: configurableField.py:291
lsst.pex.config.config.Field.__get__
def __get__(self, instance, owner=None, at=None, label="default")
Definition: config.py:550
lsst.pex.config.configurableField.ConfigurableField.ConfigClass
ConfigClass
Definition: configurableField.py:274
lsst.pex.config.configurableField.ConfigurableInstance.target
target
Definition: configurableField.py:86
lsst.pex.config.configurableField.ConfigurableInstance.__init__
def __init__(self, config, field, at=None, label="default")
Definition: configurableField.py:70
lsst.pex.config.configurableField.ConfigurableInstance.value
value
Definition: configurableField.py:94
lsst.pex.config.configurableField.ConfigurableField.save
def save(self, outfile, instance)
Definition: configurableField.py:323
type
table::Key< int > type
Definition: Detector.cc:163
lsst.pex.config.callStack.getStackFrame
def getStackFrame(relative=0)
Definition: callStack.py:58
lsst.pex.config.config.Field
Definition: config.py:247
lsst.pex.config.comparison.getComparisonName
def getComparisonName(name1, name2)
Definition: comparison.py:40
lsst.pex.config.config.Field._setup
def _setup(self, doc, dtype, default, check, optional, source, deprecated)
Definition: config.py:336
lsst.pex.config.comparison.compareConfigs
def compareConfigs(name, c1, c2, shortcut=True, rtol=1E-8, atol=1E-8, output=None)
Definition: comparison.py:111