28 __all__ = (
'ConfigurableInstance', 
'ConfigurableField')
 
   32 from .config 
import Config, Field, _joinNamePath, _typeStr, FieldValidationError
 
   33 from .comparison 
import compareConfigs, getComparisonName
 
   34 from .callStack 
import getCallStack, getStackFrame
 
   38     """A retargetable configuration in a `ConfigurableField` that proxies 
   39     a `~lsst.pex.config.Config`. 
   43     ``ConfigurableInstance`` implements ``__getattr__`` and ``__setattr__`` 
   44     methods that forward to the `~lsst.pex.config.Config` it holds. 
   45     ``ConfigurableInstance`` adds a `retarget` method. 
   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. 
   53     def __initValue(self, at, label):
 
   54         """Construct value of field. 
   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 
   62         name = _joinNamePath(self._config._name, self._field.name)
 
   64             storage = self._field.default._storage
 
   67         value = self._ConfigClass(__name=name, __at=at, __label=label, **storage)
 
   68         object.__setattr__(self, 
"_value", value)
 
   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)
 
   80         at += [self._field.source]
 
   83         history = config._history.setdefault(field.name, [])
 
   84         history.append((
"Targeted and initialized from defaults", at, label))
 
   86     target = property(
lambda x: x._target)
 
   87     """The targeted configurable (read-only). 
   90     ConfigClass = property(
lambda x: x._ConfigClass)
 
   91     """The configuration class (read-only) 
   94     value = property(
lambda x: x._value)
 
   95     """The `ConfigClass` instance (`lsst.pex.config.ConfigClass`-type, 
  100         """Call the configurable. 
  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`. 
  110     def retarget(self, target, ConfigClass=None, at=None, label="retarget"):
 
  111         """Target a new configurable and ConfigClass 
  113         if self._config._frozen:
 
  117             ConfigClass = self._field.validateTarget(target, ConfigClass)
 
  118         except BaseException 
as e:
 
  123         object.__setattr__(self, 
"_target", target)
 
  125             object.__setattr__(self, 
"_ConfigClass", ConfigClass)
 
  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))
 
  133         return getattr(self._value, name)
 
  136         """Pretend to be an instance of ConfigClass. 
  138         Attributes defined by ConfigurableInstance will shadow those defined 
  141         if self._config._frozen:
 
  144         if name 
in self.__dict__:
 
  146             object.__setattr__(self, name, value)
 
  150             self._value.
__setattr__(name, value, at=at, label=label)
 
  154         Pretend to be an isntance of  ConfigClass. 
  155         Attributes defiend by ConfigurableInstance will shadow those defined 
  158         if self._config._frozen:
 
  163             object.__delattr__(self, name)
 
  164         except AttributeError:
 
  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). 
  175     The ``ConfigurableField`` is often used to configure subtasks, which are 
  176     tasks (`~lsst.pipe.base.Task`) called by a parent task. 
  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 
  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. 
  215     You can use the `ConfigurableInstance.apply` method to construct a 
  216     fully-configured configurable. 
  220         """Validate the target and configuration class. 
  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. 
  233             Raised if ``ConfigClass`` is `None` and ``target`` does not have a 
  234             ``ConfigClass`` attribute. 
  236             Raised if ``ConfigClass`` is not a `~lsst.pex.config.Config` 
  241             - ``target`` is not callable (callables have a ``__call__`` 
  243             - ``target`` is not startically defined (does not have 
  244               ``__module__`` or ``__name__`` attributes). 
  246         if ConfigClass 
is None:
 
  248                 ConfigClass = target.ConfigClass
 
  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)")
 
  261     def __init__(self, doc, target, ConfigClass=None, default=None, check=None, deprecated=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)))
 
  271         self.
_setup(doc=doc, dtype=ConfigurableInstance, default=default,
 
  272                     check=check, optional=
False, source=source, deprecated=deprecated)
 
  276     def __getOrMake(self, instance, at=None, label="default"):
 
  277         value = instance._storage.get(self.name, 
None)
 
  282             instance._storage[self.name] = value
 
  285     def __get__(self, instance, owner=None, at=None, label="default"):
 
  286         if instance 
is None or not isinstance(instance, Config):
 
  289             return self.
__getOrMake(instance, at=at, label=label)
 
  291     def __set__(self, instance, value, at=None, label="assignment"):
 
  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)
 
  307             msg = 
"Value %s is of incorrect type %s. Expected %s" % \
 
  308                 (value, _typeStr(value), _typeStr(oldValue.ConfigClass))
 
  312         fullname = _joinNamePath(instance._name, self.name)
 
  314         value._rename(fullname)
 
  316     def _collectImports(self, instance, imports):
 
  318         target = value.target
 
  319         imports.add(target.__module__)
 
  320         value.value._collectImports()
 
  321         imports |= value.value._imports
 
  323     def save(self, outfile, instance):
 
  324         fullname = _joinNamePath(instance._name, self.name)
 
  326         target = value.target
 
  331             ConfigClass = value.ConfigClass
 
  332             outfile.write(
u"{}.retarget(target={}, ConfigClass={})\n\n".
format(fullname,
 
  334                                                                                _typeStr(ConfigClass)))
 
  344         return value.toDict()
 
  350         if self.
check is not None and not self.
check(value):
 
  351             msg = 
"%s is not a valid value" % str(value)
 
  355         """Customize deep-copying, because we always want a reference to the 
  358         WARNING: this must be overridden by subclasses if they change the 
  359         constructor signature! 
  362                           default=copy.deepcopy(self.
default))
 
  364     def _compare(self, instance1, instance2, shortcut, rtol, atol, output):
 
  365         """Compare two fields for equality. 
  367         Used by `lsst.pex.ConfigDictField.compare`. 
  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. 
  376             If `True`, this function returns as soon as an inequality if found. 
  378             Relative tolerance for floating point comparisons. 
  380             Absolute tolerance for floating point comparisons. 
  382             A callable that takes a string, used (possibly repeatedly) to 
  383             report inequalities. For example: `print`. 
  388             `True` if the fields are equal, `False` otherwise. 
  392         Floating point comparisons are performed by `numpy.allclose`. 
  394         c1 = getattr(instance1, self.name)._value
 
  395         c2 = getattr(instance2, self.name)._value
 
  397             _joinNamePath(instance1._name, self.name),
 
  398             _joinNamePath(instance2._name, self.name)
 
  400         return compareConfigs(name, c1, c2, shortcut=shortcut, rtol=rtol, atol=atol, output=output)