lsst.pex.config  18.1.0-3-g6b74884
registry.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 program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 
22 __all__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable")
23 
24 import collections.abc
25 import copy
26 
27 from .config import Config, FieldValidationError, _typeStr
28 from .configChoiceField import ConfigInstanceDict, ConfigChoiceField
29 
30 
32  """A wrapper for configurables.
33 
34  Used for configurables that don't contain a ``ConfigClass`` attribute,
35  or contain one that is being overridden.
36  """
37 
38  def __init__(self, target, ConfigClass):
39  self.ConfigClass = ConfigClass
40  self._target = target
41 
42  def __call__(self, *args, **kwargs):
43  return self._target(*args, **kwargs)
44 
45 
46 class Registry(collections.abc.Mapping):
47  """A base class for global registries, which map names to configurables.
48 
49  A registry acts like a read-only dictionary with an additional `register`
50  method to add targets. Targets in the registry are configurables (see
51  *Notes*).
52 
53  Parameters
54  ----------
55  configBaseType : `lsst.pex.config.Config`-type
56  The base class for config classes in the registry.
57 
58  Notes
59  -----
60  A configurable is a callable with call signature ``(config, *args)``
61  Configurables typically create an algorithm or are themselves the
62  algorithm. Often configurables are `lsst.pipe.base.Task` subclasses, but
63  this is not required.
64 
65  A ``Registry`` has these requirements:
66 
67  - All configurables added to a particular registry have the same call
68  signature.
69  - All configurables in a registry typically share something important
70  in common. For example, all configurables in ``psfMatchingRegistry``
71  return a PSF matching class that has a ``psfMatch`` method with a
72  particular call signature.
73 
74  Examples
75  --------
76  This examples creates a configurable class ``Foo`` and adds it to a
77  registry. First, creating the configurable:
78 
79  >>> from lsst.pex.config import Registry, Config
80  >>> class FooConfig(Config):
81  ... val = Field(dtype=int, default=3, doc="parameter for Foo")
82  ...
83  >>> class Foo:
84  ... ConfigClass = FooConfig
85  ... def __init__(self, config):
86  ... self.config = config
87  ... def addVal(self, num):
88  ... return self.config.val + num
89  ...
90 
91  Next, create a ``Registry`` instance called ``registry`` and register the
92  ``Foo`` configurable under the ``"foo"`` key:
93 
94  >>> registry = Registry()
95  >>> registry.register("foo", Foo)
96  >>> print(list(registry.keys()))
97  ["foo"]
98 
99  Now ``Foo`` is conveniently accessible from the registry itself.
100 
101  Finally, use the registry to get the configurable class and create an
102  instance of it:
103 
104  >>> FooConfigurable = registry["foo"]
105  >>> foo = FooConfigurable(FooConfigurable.ConfigClass())
106  >>> foo.addVal(5)
107  8
108  """
109 
110  def __init__(self, configBaseType=Config):
111  if not issubclass(configBaseType, Config):
112  raise TypeError("configBaseType=%s must be a subclass of Config" % _typeStr(configBaseType,))
113  self._configBaseType = configBaseType
114  self._dict = {}
115 
116  def register(self, name, target, ConfigClass=None):
117  """Add a new configurable target to the registry.
118 
119  Parameters
120  ----------
121  name : `str`
122  Name that the ``target`` is registered under. The target can
123  be accessed later with `dict`-like patterns using ``name`` as
124  the key.
125  target : obj
126  A configurable type, usually a subclass of `lsst.pipe.base.Task`.
127  ConfigClass : `lsst.pex.config.Config`-type, optional
128  A subclass of `lsst.pex.config.Config` used to configure the
129  configurable. If `None` then the configurable's ``ConfigClass``
130  attribute is used.
131 
132  Raises
133  ------
134  RuntimeError
135  Raised if an item with ``name`` is already in the registry.
136  AttributeError
137  Raised if ``ConfigClass`` is `None` and ``target`` does not have
138  a ``ConfigClass`` attribute.
139 
140  Notes
141  -----
142  If ``ConfigClass`` is provided then the ``target`` configurable is
143  wrapped in a new object that forwards function calls to it. Otherwise
144  the original ``target`` is stored.
145  """
146  if name in self._dict:
147  raise RuntimeError("An item with name %r already exists" % name)
148  if ConfigClass is None:
149  wrapper = target
150  else:
151  wrapper = ConfigurableWrapper(target, ConfigClass)
152  if not issubclass(wrapper.ConfigClass, self._configBaseType):
153  raise TypeError("ConfigClass=%s is not a subclass of %r" %
154  (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType)))
155  self._dict[name] = wrapper
156 
157  def __getitem__(self, key):
158  return self._dict[key]
159 
160  def __len__(self):
161  return len(self._dict)
162 
163  def __iter__(self):
164  return iter(self._dict)
165 
166  def __contains__(self, key):
167  return key in self._dict
168 
169  def makeField(self, doc, default=None, optional=False, multi=False):
170  """Create a `RegistryField` configuration field from this registry.
171 
172  Parameters
173  ----------
174  doc : `str`
175  A description of the field.
176  default : object, optional
177  The default target for the field.
178  optional : `bool`, optional
179  When `False`, `lsst.pex.config.Config.validate` fails if the
180  field's value is `None`.
181  multi : `bool`, optional
182  A flag to allow multiple selections in the `RegistryField` if
183  `True`.
184 
185  Returns
186  -------
187  field : `lsst.pex.config.RegistryField`
188  `~lsst.pex.config.RegistryField` Configuration field.
189  """
190  return RegistryField(doc, self, default, optional, multi)
191 
192 
193 class RegistryAdaptor(collections.abc.Mapping):
194  """Private class that makes a `Registry` behave like the thing a
195  `~lsst.pex.config.ConfigChoiceField` expects.
196 
197  Parameters
198  ----------
199  registry : `Registry`
200  `Registry` instance.
201  """
202 
203  def __init__(self, registry):
204  self.registry = registry
205 
206  def __getitem__(self, k):
207  return self.registry[k].ConfigClass
208 
209  def __iter__(self):
210  return iter(self.registry)
211 
212  def __len__(self):
213  return len(self.registry)
214 
215  def __contains__(self, k):
216  return k in self.registry
217 
218 
220  """Dictionary of instantiated configs, used to populate a `RegistryField`.
221 
222  Parameters
223  ----------
224  config : `lsst.pex.config.Config`
225  Configuration instance.
226  field : `RegistryField`
227  Configuration field.
228  """
229 
230  def __init__(self, config, field):
231  ConfigInstanceDict.__init__(self, config, field)
232  self.registry = field.registry
233 
234  def _getTarget(self):
235  if self._field.multi:
236  raise FieldValidationError(self._field, self._config,
237  "Multi-selection field has no attribute 'target'")
238  return self._field.typemap.registry[self._selection]
239 
240  target = property(_getTarget)
241 
242  def _getTargets(self):
243  if not self._field.multi:
244  raise FieldValidationError(self._field, self._config,
245  "Single-selection field has no attribute 'targets'")
246  return [self._field.typemap.registry[c] for c in self._selection]
247 
248  targets = property(_getTargets)
249 
250  def apply(self, *args, **kw):
251  """Call the active target(s) with the active config as a keyword arg
252 
253  If this is a multi-selection field, return a list obtained by calling
254  each active target with its corresponding active config.
255 
256  Additional arguments will be passed on to the configurable target(s)
257  """
258  if self.active is None:
259  msg = "No selection has been made. Options: %s" % \
260  (" ".join(list(self._field.typemap.registry.keys())))
261  raise FieldValidationError(self._field, self._config, msg)
262  if self._field.multi:
263  retvals = []
264  for c in self._selection:
265  retvals.append(self._field.typemap.registry[c](*args, config=self[c], **kw))
266  return retvals
267  else:
268  return self._field.typemap.registry[self.name](*args, config=self[self.name], **kw)
269 
270  def __setattr__(self, attr, value):
271  if attr == "registry":
272  object.__setattr__(self, attr, value)
273  else:
274  ConfigInstanceDict.__setattr__(self, attr, value)
275 
276 
278  """A configuration field whose options are defined in a `Registry`.
279 
280  Parameters
281  ----------
282  doc : `str`
283  A description of the field.
284  registry : `Registry`
285  The registry that contains this field.
286  default : `str`, optional
287  The default target key.
288  optional : `bool`, optional
289  When `False`, `lsst.pex.config.Config.validate` fails if the field's
290  value is `None`.
291  multi : `bool`, optional
292  If `True`, the field allows multiple selections. The default is
293  `False`.
294 
295  See also
296  --------
297  ChoiceField
298  ConfigChoiceField
299  ConfigDictField
300  ConfigField
301  ConfigurableField
302  DictField
303  Field
304  ListField
305  RangeField
306  """
307 
308  instanceDictClass = RegistryInstanceDict
309  """Class used to hold configurable instances in the field.
310  """
311 
312  def __init__(self, doc, registry, default=None, optional=False, multi=False):
313  types = RegistryAdaptor(registry)
314  self.registry = registry
315  ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
316 
317  def __deepcopy__(self, memo):
318  """Customize deep-copying, want a reference to the original registry.
319 
320  WARNING: this must be overridden by subclasses if they change the
321  constructor signature!
322  """
323  other = type(self)(doc=self.doc, registry=self.registry,
324  default=copy.deepcopy(self.default),
325  optional=self.optional, multi=self.multi)
326  other.source = self.source
327  return other
328 
329 
330 def makeRegistry(doc, configBaseType=Config):
331  """Create a `Registry`.
332 
333  Parameters
334  ----------
335  doc : `str`
336  Docstring for the created `Registry` (this is set as the ``__doc__``
337  attribute of the `Registry` instance.
338  configBaseType : `lsst.pex.config.Config`-type
339  Base type of config classes in the `Registry`
340  (`lsst.pex.config.Registry.configBaseType`).
341 
342  Returns
343  -------
344  registry : `Registry`
345  Registry with ``__doc__`` and `~Registry.configBaseType` attributes
346  set.
347  """
348  cls = type("Registry", (Registry,), {"__doc__": doc})
349  return cls(configBaseType=configBaseType)
350 
351 
352 def registerConfigurable(name, registry, ConfigClass=None):
353  """A decorator that adds a class as a configurable in a `Registry`
354  instance.
355 
356  Parameters
357  ----------
358  name : `str`
359  Name of the target (the decorated class) in the ``registry``.
360  registry : `Registry`
361  The `Registry` instance that the decorated class is added to.
362  ConfigClass : `lsst.pex.config.Config`-type, optional
363  Config class associated with the configurable. If `None`, the class's
364  ``ConfigClass`` attribute is used instead.
365 
366  See also
367  --------
368  registerConfig
369 
370  Notes
371  -----
372  Internally, this decorator runs `Registry.register`.
373  """
374  def decorate(cls):
375  registry.register(name, target=cls, ConfigClass=ConfigClass)
376  return cls
377  return decorate
378 
379 
380 def registerConfig(name, registry, target):
381  """Decorator that adds a class as a ``ConfigClass`` in a `Registry` and
382  associates it with the given configurable.
383 
384  Parameters
385  ----------
386  name : `str`
387  Name of the ``target`` in the ``registry``.
388  registry : `Registry`
389  The registry containing the ``target``.
390  target : obj
391  A configurable type, such as a subclass of `lsst.pipe.base.Task`.
392 
393  See also
394  --------
395  registerConfigurable
396 
397  Notes
398  -----
399  Internally, this decorator runs `Registry.register`.
400  """
401  def decorate(cls):
402  registry.register(name, target=target, ConfigClass=cls)
403  return cls
404  return decorate
def __call__(self, args, kwargs)
Definition: registry.py:42
def registerConfigurable(name, registry, ConfigClass=None)
Definition: registry.py:352
def registerConfig(name, registry, target)
Definition: registry.py:380
def __contains__(self, key)
Definition: registry.py:166
def makeField(self, doc, default=None, optional=False, multi=False)
Definition: registry.py:169
def __init__(self, registry)
Definition: registry.py:203
def __setattr__(self, attr, value)
Definition: registry.py:270
def __init__(self, target, ConfigClass)
Definition: registry.py:38
def __init__(self, doc, registry, default=None, optional=False, multi=False)
Definition: registry.py:312
def __init__(self, config, field)
Definition: registry.py:230
def register(self, name, target, ConfigClass=None)
Definition: registry.py:116
def __getitem__(self, key)
Definition: registry.py:157
def makeRegistry(doc, configBaseType=Config)
Definition: registry.py:330
def __init__(self, configBaseType=Config)
Definition: registry.py:110