LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
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
30import collections.abc
31import copy
32
33from .config import Config, FieldValidationError, _typeStr
34from .configChoiceField import ConfigChoiceField, ConfigInstanceDict
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
52class 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(
119 "configBaseType=%s must be a subclass of Config"
120 % _typeStr(
121 configBaseType,
122 )
123 )
124 self._configBaseType_configBaseType = configBaseType
125 self._dict_dict = {}
126
127 def register(self, name, target, ConfigClass=None):
128 """Add a new configurable target to the registry.
129
130 Parameters
131 ----------
132 name : `str`
133 Name that the ``target`` is registered under. The target can
134 be accessed later with `dict`-like patterns using ``name`` as
135 the key.
136 target : obj
137 A configurable type, usually a subclass of `lsst.pipe.base.Task`.
138 ConfigClass : `lsst.pex.config.Config`-type, optional
139 A subclass of `lsst.pex.config.Config` used to configure the
140 configurable. If `None` then the configurable's ``ConfigClass``
141 attribute is used.
142
143 Raises
144 ------
145 RuntimeError
146 Raised if an item with ``name`` is already in the registry.
147 AttributeError
148 Raised if ``ConfigClass`` is `None` and ``target`` does not have
149 a ``ConfigClass`` attribute.
150
151 Notes
152 -----
153 If ``ConfigClass`` is provided then the ``target`` configurable is
154 wrapped in a new object that forwards function calls to it. Otherwise
155 the original ``target`` is stored.
156 """
157 if name in self._dict_dict:
158 raise RuntimeError("An item with name %r already exists" % name)
159 if ConfigClass is None:
160 wrapper = target
161 else:
162 wrapper = ConfigurableWrapper(target, ConfigClass)
163 if not issubclass(wrapper.ConfigClass, self._configBaseType_configBaseType):
164 raise TypeError(
165 "ConfigClass=%s is not a subclass of %r"
166 % (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType_configBaseType))
167 )
168 self._dict_dict[name] = wrapper
169
170 def __getitem__(self, key):
171 return self._dict_dict[key]
172
173 def __len__(self):
174 return len(self._dict_dict)
175
176 def __iter__(self):
177 return iter(self._dict_dict)
178
179 def __contains__(self, key):
180 return key in self._dict_dict
181
182 def makeField(self, doc, default=None, optional=False, multi=False):
183 """Create a `RegistryField` configuration field from this registry.
184
185 Parameters
186 ----------
187 doc : `str`
188 A description of the field.
189 default : object, optional
190 The default target for the field.
191 optional : `bool`, optional
192 When `False`, `lsst.pex.config.Config.validate` fails if the
193 field's value is `None`.
194 multi : `bool`, optional
195 A flag to allow multiple selections in the `RegistryField` if
196 `True`.
197
198 Returns
199 -------
201 `~lsst.pex.config.RegistryField` Configuration field.
202 """
203 return RegistryField(doc, self, default, optional, multi)
204
205
206class RegistryAdaptor(collections.abc.Mapping):
207 """Private class that makes a `Registry` behave like the thing a
209
210 Parameters
211 ----------
212 registry : `Registry`
213 `Registry` instance.
214 """
215
216 def __init__(self, registry):
217 self.registryregistry = registry
218
219 def __getitem__(self, k):
220 return self.registryregistry[k].ConfigClass
221
222 def __iter__(self):
223 return iter(self.registryregistry)
224
225 def __len__(self):
226 return len(self.registryregistry)
227
228 def __contains__(self, k):
229 return k in self.registryregistry
230
231
233 """Dictionary of instantiated configs, used to populate a `RegistryField`.
234
235 Parameters
236 ----------
237 config : `lsst.pex.config.Config`
238 Configuration instance.
239 field : `RegistryField`
240 Configuration field.
241 """
242
243 def __init__(self, config, field):
244 ConfigInstanceDict.__init__(self, config, field)
245 self.registryregistry = field.registry
246
247 def _getTarget(self):
248 if self._field_field.multi:
250 self._field_field, self._config_config, "Multi-selection field has no attribute 'target'"
251 )
252 return self.typestypes.registry[self._selection_selection]
253
254 target = property(_getTarget)
255
256 def _getTargets(self):
257 if not self._field_field.multi:
259 self._field_field, self._config_config, "Single-selection field has no attribute 'targets'"
260 )
261 return [self.typestypes.registry[c] for c in self._selection_selection]
262
263 targets = property(_getTargets)
264
265 def apply(self, *args, **kw):
266 """Call the active target(s) with the active config as a keyword arg
267
268 If this is a multi-selection field, return a list obtained by calling
269 each active target with its corresponding active config.
270
271 Additional arguments will be passed on to the configurable target(s)
272 """
273 if self.activeactive is None:
274 msg = "No selection has been made. Options: %s" % " ".join(self.typestypes.registry.keys())
275 raise FieldValidationError(self._field_field, self._config_config, msg)
276 if self._field_field.multi:
277 retvals = []
278 for c in self._selection_selection:
279 retvals.append(self.typestypes.registry[c](*args, config=self[c], **kw))
280 return retvals
281 else:
282 return self.typestypes.registry[self.namename](*args, config=self[self.namename], **kw)
283
284 def __setattr__(self, attr, value):
285 if attr == "registry":
286 object.__setattr__(self, attr, value)
287 else:
288 ConfigInstanceDict.__setattr__(self, attr, value)
289
290
292 """A configuration field whose options are defined in a `Registry`.
293
294 Parameters
295 ----------
296 doc : `str`
297 A description of the field.
298 registry : `Registry`
299 The registry that contains this field.
300 default : `str`, optional
301 The default target key.
302 optional : `bool`, optional
303 When `False`, `lsst.pex.config.Config.validate` fails if the field's
304 value is `None`.
305 multi : `bool`, optional
306 If `True`, the field allows multiple selections. The default is
307 `False`.
308
309 See also
310 --------
311 ChoiceField
312 ConfigChoiceField
313 ConfigDictField
314 ConfigField
315 ConfigurableField
316 DictField
317 Field
318 ListField
319 RangeField
320 """
321
322 instanceDictClass = RegistryInstanceDict
323 """Class used to hold configurable instances in the field.
324 """
325
326 def __init__(self, doc, registry, default=None, optional=False, multi=False):
327 types = RegistryAdaptor(registry)
328 self.registryregistry = registry
329 ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
330
331 def __deepcopy__(self, memo):
332 """Customize deep-copying, want a reference to the original registry.
333
334 WARNING: this must be overridden by subclasses if they change the
335 constructor signature!
336 """
337 other = type(self)(
338 doc=self.docdoc,
339 registry=self.registryregistry,
340 default=copy.deepcopy(self.defaultdefault),
341 optional=self.optionaloptional,
342 multi=self.multimulti,
343 )
344 other.source = self.sourcesource
345 return other
346
347
348def makeRegistry(doc, configBaseType=Config):
349 """Create a `Registry`.
350
351 Parameters
352 ----------
353 doc : `str`
354 Docstring for the created `Registry` (this is set as the ``__doc__``
355 attribute of the `Registry` instance.
356 configBaseType : `lsst.pex.config.Config`-type
357 Base type of config classes in the `Registry`
358 (`lsst.pex.config.Registry.configBaseType`).
359
360 Returns
361 -------
362 registry : `Registry`
363 Registry with ``__doc__`` and `~Registry.configBaseType` attributes
364 set.
365 """
366 cls = type("Registry", (Registry,), {"__doc__": doc})
367 return cls(configBaseType=configBaseType)
368
369
370def registerConfigurable(name, registry, ConfigClass=None):
371 """A decorator that adds a class as a configurable in a `Registry`
372 instance.
373
374 Parameters
375 ----------
376 name : `str`
377 Name of the target (the decorated class) in the ``registry``.
378 registry : `Registry`
379 The `Registry` instance that the decorated class is added to.
380 ConfigClass : `lsst.pex.config.Config`-type, optional
381 Config class associated with the configurable. If `None`, the class's
382 ``ConfigClass`` attribute is used instead.
383
384 See also
385 --------
386 registerConfig
387
388 Notes
389 -----
390 Internally, this decorator runs `Registry.register`.
391 """
392
393 def decorate(cls):
394 registry.register(name, target=cls, ConfigClass=ConfigClass)
395 return cls
396
397 return decorate
398
399
400def registerConfig(name, registry, target):
401 """Decorator that adds a class as a ``ConfigClass`` in a `Registry` and
402 associates it with the given configurable.
403
404 Parameters
405 ----------
406 name : `str`
407 Name of the ``target`` in the ``registry``.
408 registry : `Registry`
409 The registry containing the ``target``.
410 target : obj
411 A configurable type, such as a subclass of `lsst.pipe.base.Task`.
412
413 See also
414 --------
415 registerConfigurable
416
417 Notes
418 -----
419 Internally, this decorator runs `Registry.register`.
420 """
421
422 def decorate(cls):
423 registry.register(name, target=target, ConfigClass=cls)
424 return cls
425
426 return decorate
table::Key< int > type
Definition: Detector.cc:163
table::Key< int > to
table::Key< int > a
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:326
def register(self, name, target, ConfigClass=None)
Definition: registry.py:127
def makeField(self, doc, default=None, optional=False, multi=False)
Definition: registry.py:182
def __init__(self, configBaseType=Config)
Definition: registry.py:116
daf::base::PropertyList * list
Definition: fits.cc:913
def registerConfigurable(name, registry, ConfigClass=None)
Definition: registry.py:370
def registerConfig(name, registry, target)
Definition: registry.py:400
def makeRegistry(doc, configBaseType=Config)
Definition: registry.py:348