LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
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 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__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable")
29 
30 import collections.abc
31 import copy
32 
33 from .config import Config, FieldValidationError, _typeStr
34 from .configChoiceField import ConfigInstanceDict, ConfigChoiceField
35 
36 
38  """A wrapper for configurables.
39 
40  Used for configurables that don't contain a ``ConfigClass`` attribute,
41  or contain one that is being overridden.
42  """
43 
44  def __init__(self, target, ConfigClass):
45  self.ConfigClassConfigClass = ConfigClass
46  self._target_target = target
47 
48  def __call__(self, *args, **kwargs):
49  return self._target_target(*args, **kwargs)
50 
51 
52 class Registry(collections.abc.Mapping):
53  """A base class for global registries, which map names to configurables.
54 
55  A registry acts like a read-only dictionary with an additional `register`
56  method to add targets. Targets in the registry are configurables (see
57  *Notes*).
58 
59  Parameters
60  ----------
61  configBaseType : `lsst.pex.config.Config`-type
62  The base class for config classes in the registry.
63 
64  Notes
65  -----
66  A configurable is a callable with call signature ``(config, *args)``
67  Configurables typically create an algorithm or are themselves the
68  algorithm. Often configurables are `lsst.pipe.base.Task` subclasses, but
69  this is not required.
70 
71  A ``Registry`` has these requirements:
72 
73  - All configurables added to a particular registry have the same call
74  signature.
75  - All configurables in a registry typically share something important
76  in common. For example, all configurables in ``psfMatchingRegistry``
77  return a PSF matching class that has a ``psfMatch`` method with a
78  particular call signature.
79 
80  Examples
81  --------
82  This examples creates a configurable class ``Foo`` and adds it to a
83  registry. First, creating the configurable:
84 
85  >>> from lsst.pex.config import Registry, Config
86  >>> class FooConfig(Config):
87  ... val = Field(dtype=int, default=3, doc="parameter for Foo")
88  ...
89  >>> class Foo:
90  ... ConfigClass = FooConfig
91  ... def __init__(self, config):
92  ... self.config = config
93  ... def addVal(self, num):
94  ... return self.config.val + num
95  ...
96 
97  Next, create a ``Registry`` instance called ``registry`` and register the
98  ``Foo`` configurable under the ``"foo"`` key:
99 
100  >>> registry = Registry()
101  >>> registry.register("foo", Foo)
102  >>> print(list(registry.keys()))
103  ["foo"]
104 
105  Now ``Foo`` is conveniently accessible from the registry itself.
106 
107  Finally, use the registry to get the configurable class and create an
108  instance of it:
109 
110  >>> FooConfigurable = registry["foo"]
111  >>> foo = FooConfigurable(FooConfigurable.ConfigClass())
112  >>> foo.addVal(5)
113  8
114  """
115 
116  def __init__(self, configBaseType=Config):
117  if not issubclass(configBaseType, Config):
118  raise TypeError("configBaseType=%s must be a subclass of Config" % _typeStr(configBaseType,))
119  self._configBaseType_configBaseType = configBaseType
120  self._dict_dict = {}
121 
122  def register(self, name, target, ConfigClass=None):
123  """Add a new configurable target to the registry.
124 
125  Parameters
126  ----------
127  name : `str`
128  Name that the ``target`` is registered under. The target can
129  be accessed later with `dict`-like patterns using ``name`` as
130  the key.
131  target : obj
132  A configurable type, usually a subclass of `lsst.pipe.base.Task`.
133  ConfigClass : `lsst.pex.config.Config`-type, optional
134  A subclass of `lsst.pex.config.Config` used to configure the
135  configurable. If `None` then the configurable's ``ConfigClass``
136  attribute is used.
137 
138  Raises
139  ------
140  RuntimeError
141  Raised if an item with ``name`` is already in the registry.
142  AttributeError
143  Raised if ``ConfigClass`` is `None` and ``target`` does not have
144  a ``ConfigClass`` attribute.
145 
146  Notes
147  -----
148  If ``ConfigClass`` is provided then the ``target`` configurable is
149  wrapped in a new object that forwards function calls to it. Otherwise
150  the original ``target`` is stored.
151  """
152  if name in self._dict_dict:
153  raise RuntimeError("An item with name %r already exists" % name)
154  if ConfigClass is None:
155  wrapper = target
156  else:
157  wrapper = ConfigurableWrapper(target, ConfigClass)
158  if not issubclass(wrapper.ConfigClass, self._configBaseType_configBaseType):
159  raise TypeError("ConfigClass=%s is not a subclass of %r" %
160  (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType_configBaseType)))
161  self._dict_dict[name] = wrapper
162 
163  def __getitem__(self, key):
164  return self._dict_dict[key]
165 
166  def __len__(self):
167  return len(self._dict_dict)
168 
169  def __iter__(self):
170  return iter(self._dict_dict)
171 
172  def __contains__(self, key):
173  return key in self._dict_dict
174 
175  def makeField(self, doc, default=None, optional=False, multi=False):
176  """Create a `RegistryField` configuration field from this registry.
177 
178  Parameters
179  ----------
180  doc : `str`
181  A description of the field.
182  default : object, optional
183  The default target for the field.
184  optional : `bool`, optional
185  When `False`, `lsst.pex.config.Config.validate` fails if the
186  field's value is `None`.
187  multi : `bool`, optional
188  A flag to allow multiple selections in the `RegistryField` if
189  `True`.
190 
191  Returns
192  -------
193  field : `lsst.pex.config.RegistryField`
194  `~lsst.pex.config.RegistryField` Configuration field.
195  """
196  return RegistryField(doc, self, default, optional, multi)
197 
198 
199 class RegistryAdaptor(collections.abc.Mapping):
200  """Private class that makes a `Registry` behave like the thing a
201  `~lsst.pex.config.ConfigChoiceField` expects.
202 
203  Parameters
204  ----------
205  registry : `Registry`
206  `Registry` instance.
207  """
208 
209  def __init__(self, registry):
210  self.registryregistry = registry
211 
212  def __getitem__(self, k):
213  return self.registryregistry[k].ConfigClass
214 
215  def __iter__(self):
216  return iter(self.registryregistry)
217 
218  def __len__(self):
219  return len(self.registryregistry)
220 
221  def __contains__(self, k):
222  return k in self.registryregistry
223 
224 
226  """Dictionary of instantiated configs, used to populate a `RegistryField`.
227 
228  Parameters
229  ----------
230  config : `lsst.pex.config.Config`
231  Configuration instance.
232  field : `RegistryField`
233  Configuration field.
234  """
235 
236  def __init__(self, config, field):
237  ConfigInstanceDict.__init__(self, config, field)
238  self.registryregistry = field.registry
239 
240  def _getTarget(self):
241  if self._field_field.multi:
242  raise FieldValidationError(self._field_field, self._config_config,
243  "Multi-selection field has no attribute 'target'")
244  return self.typestypes.registry[self._selection_selection]
245 
246  target = property(_getTarget)
247 
248  def _getTargets(self):
249  if not self._field_field.multi:
250  raise FieldValidationError(self._field_field, self._config_config,
251  "Single-selection field has no attribute 'targets'")
252  return [self.typestypes.registry[c] for c in self._selection_selection]
253 
254  targets = property(_getTargets)
255 
256  def apply(self, *args, **kw):
257  """Call the active target(s) with the active config as a keyword arg
258 
259  If this is a multi-selection field, return a list obtained by calling
260  each active target with its corresponding active config.
261 
262  Additional arguments will be passed on to the configurable target(s)
263  """
264  if self.activeactive is None:
265  msg = "No selection has been made. Options: %s" % \
266  " ".join(self.typestypes.registry.keys())
267  raise FieldValidationError(self._field_field, self._config_config, msg)
268  if self._field_field.multi:
269  retvals = []
270  for c in self._selection_selection:
271  retvals.append(self.typestypes.registry[c](*args, config=self[c], **kw))
272  return retvals
273  else:
274  return self.typestypes.registry[self.namename](*args, config=self[self.namename], **kw)
275 
276  def __setattr__(self, attr, value):
277  if attr == "registry":
278  object.__setattr__(self, attr, value)
279  else:
280  ConfigInstanceDict.__setattr__(self, attr, value)
281 
282 
284  """A configuration field whose options are defined in a `Registry`.
285 
286  Parameters
287  ----------
288  doc : `str`
289  A description of the field.
290  registry : `Registry`
291  The registry that contains this field.
292  default : `str`, optional
293  The default target key.
294  optional : `bool`, optional
295  When `False`, `lsst.pex.config.Config.validate` fails if the field's
296  value is `None`.
297  multi : `bool`, optional
298  If `True`, the field allows multiple selections. The default is
299  `False`.
300 
301  See also
302  --------
303  ChoiceField
304  ConfigChoiceField
305  ConfigDictField
306  ConfigField
307  ConfigurableField
308  DictField
309  Field
310  ListField
311  RangeField
312  """
313 
314  instanceDictClass = RegistryInstanceDict
315  """Class used to hold configurable instances in the field.
316  """
317 
318  def __init__(self, doc, registry, default=None, optional=False, multi=False):
319  types = RegistryAdaptor(registry)
320  self.registryregistry = registry
321  ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
322 
323  def __deepcopy__(self, memo):
324  """Customize deep-copying, want a reference to the original registry.
325 
326  WARNING: this must be overridden by subclasses if they change the
327  constructor signature!
328  """
329  other = type(self)(doc=self.docdoc, registry=self.registryregistry,
330  default=copy.deepcopy(self.defaultdefault),
331  optional=self.optionaloptional, multi=self.multimulti)
332  other.source = self.sourcesource
333  return other
334 
335 
336 def makeRegistry(doc, configBaseType=Config):
337  """Create a `Registry`.
338 
339  Parameters
340  ----------
341  doc : `str`
342  Docstring for the created `Registry` (this is set as the ``__doc__``
343  attribute of the `Registry` instance.
344  configBaseType : `lsst.pex.config.Config`-type
345  Base type of config classes in the `Registry`
346  (`lsst.pex.config.Registry.configBaseType`).
347 
348  Returns
349  -------
350  registry : `Registry`
351  Registry with ``__doc__`` and `~Registry.configBaseType` attributes
352  set.
353  """
354  cls = type("Registry", (Registry,), {"__doc__": doc})
355  return cls(configBaseType=configBaseType)
356 
357 
358 def registerConfigurable(name, registry, ConfigClass=None):
359  """A decorator that adds a class as a configurable in a `Registry`
360  instance.
361 
362  Parameters
363  ----------
364  name : `str`
365  Name of the target (the decorated class) in the ``registry``.
366  registry : `Registry`
367  The `Registry` instance that the decorated class is added to.
368  ConfigClass : `lsst.pex.config.Config`-type, optional
369  Config class associated with the configurable. If `None`, the class's
370  ``ConfigClass`` attribute is used instead.
371 
372  See also
373  --------
374  registerConfig
375 
376  Notes
377  -----
378  Internally, this decorator runs `Registry.register`.
379  """
380  def decorate(cls):
381  registry.register(name, target=cls, ConfigClass=ConfigClass)
382  return cls
383  return decorate
384 
385 
386 def registerConfig(name, registry, target):
387  """Decorator that adds a class as a ``ConfigClass`` in a `Registry` and
388  associates it with the given configurable.
389 
390  Parameters
391  ----------
392  name : `str`
393  Name of the ``target`` in the ``registry``.
394  registry : `Registry`
395  The registry containing the ``target``.
396  target : obj
397  A configurable type, such as a subclass of `lsst.pipe.base.Task`.
398 
399  See also
400  --------
401  registerConfigurable
402 
403  Notes
404  -----
405  Internally, this decorator runs `Registry.register`.
406  """
407  def decorate(cls):
408  registry.register(name, target=target, ConfigClass=cls)
409  return cls
410  return decorate
table::Key< int > type
Definition: Detector.cc:163
def __init__(self, target, ConfigClass)
Definition: registry.py:44
def __call__(self, *args, **kwargs)
Definition: registry.py:48
def __init__(self, doc, registry, default=None, optional=False, multi=False)
Definition: registry.py:318
def register(self, name, target, ConfigClass=None)
Definition: registry.py:122
def makeField(self, doc, default=None, optional=False, multi=False)
Definition: registry.py:175
def __init__(self, configBaseType=Config)
Definition: registry.py:116
def registerConfigurable(name, registry, ConfigClass=None)
Definition: registry.py:358
def registerConfig(name, registry, target)
Definition: registry.py:386
def makeRegistry(doc, configBaseType=Config)
Definition: registry.py:336