LSST Applications g06d8191974+de063e15a7,g180d380827+d0b6459378,g2079a07aa2+86d27d4dc4,g2305ad1205+f1ae3263cc,g29320951ab+5752d78b6e,g2bbee38e9b+85cf0a37e7,g337abbeb29+85cf0a37e7,g33d1c0ed96+85cf0a37e7,g3a166c0a6a+85cf0a37e7,g3ddfee87b4+b5254b9343,g48712c4677+9ea88d309d,g487adcacf7+05f7dba17f,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+48904e3942,g64a986408d+de063e15a7,g858d7b2824+de063e15a7,g864b0138d7+33ab2bc355,g8a8a8dda67+585e252eca,g99cad8db69+4508353287,g9c22b2923f+53520f316c,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+ccb7f83a87,gc120e1dc64+6caf640b9b,gc28159a63d+85cf0a37e7,gc3e9b769f7+548c5e05a3,gcf0d15dbbd+b5254b9343,gdaeeff99f8+f9a426f77a,ge6526c86ff+515b6c9330,ge79ae78c31+85cf0a37e7,gee10cc3b42+585e252eca,gff1a9f87cc+de063e15a7,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
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 Parameters
44 ----------
45 target : configurable class
46 Target class.
47 ConfigClass : `type`
48 Config class.
49 """
50
51 def __init__(self, target, ConfigClass):
52 self.ConfigClass = ConfigClass
53 self._target = target
54
55 def __call__(self, *args, **kwargs):
56 return self._target(*args, **kwargs)
57
58
59class Registry(collections.abc.Mapping):
60 """A base class for global registries, which map names to configurables.
61
62 A registry acts like a read-only dictionary with an additional `register`
63 method to add targets. Targets in the registry are configurables (see
64 *Notes*).
65
66 Parameters
67 ----------
68 configBaseType : `lsst.pex.config.Config`-type
69 The base class for config classes in the registry.
70
71 Notes
72 -----
73 A configurable is a callable with call signature ``(config, *args)``
74 Configurables typically create an algorithm or are themselves the
75 algorithm. Often configurables are `lsst.pipe.base.Task` subclasses, but
76 this is not required.
77
78 A ``Registry`` has these requirements:
79
80 - All configurables added to a particular registry have the same call
81 signature.
82 - All configurables in a registry typically share something important
83 in common. For example, all configurables in ``psfMatchingRegistry``
84 return a PSF matching class that has a ``psfMatch`` method with a
85 particular call signature.
86
87 Examples
88 --------
89 This examples creates a configurable class ``Foo`` and adds it to a
90 registry. First, creating the configurable:
91
92 >>> from lsst.pex.config import Registry, Config
93 >>> class FooConfig(Config):
94 ... val = Field(dtype=int, default=3, doc="parameter for Foo")
95 ...
96 >>> class Foo:
97 ... ConfigClass = FooConfig
98 ... def __init__(self, config):
99 ... self.config = config
100 ... def addVal(self, num):
101 ... return self.config.val + num
102 ...
103
104 Next, create a ``Registry`` instance called ``registry`` and register the
105 ``Foo`` configurable under the ``"foo"`` key:
106
107 >>> registry = Registry()
108 >>> registry.register("foo", Foo)
109 >>> print(list(registry.keys()))
110 ["foo"]
111
112 Now ``Foo`` is conveniently accessible from the registry itself.
113
114 Finally, use the registry to get the configurable class and create an
115 instance of it:
116
117 >>> FooConfigurable = registry["foo"]
118 >>> foo = FooConfigurable(FooConfigurable.ConfigClass())
119 >>> foo.addVal(5)
120 8
121 """
122
123 def __init__(self, configBaseType=Config):
124 if not issubclass(configBaseType, Config):
125 raise TypeError(
126 "configBaseType=%s must be a subclass of Config"
127 % _typeStr(
128 configBaseType,
129 )
130 )
131 self._configBaseType = configBaseType
132 self._dict = {}
133
134 def register(self, name, target, ConfigClass=None):
135 """Add a new configurable target to the registry.
136
137 Parameters
138 ----------
139 name : `str`
140 Name that the ``target`` is registered under. The target can
141 be accessed later with `dict`-like patterns using ``name`` as
142 the key.
143 target : obj
144 A configurable type, usually a subclass of `lsst.pipe.base.Task`.
145 ConfigClass : `lsst.pex.config.Config`-type, optional
146 A subclass of `lsst.pex.config.Config` used to configure the
147 configurable. If `None` then the configurable's ``ConfigClass``
148 attribute is used.
149
150 Raises
151 ------
152 RuntimeError
153 Raised if an item with ``name`` is already in the registry.
154 AttributeError
155 Raised if ``ConfigClass`` is `None` and ``target`` does not have
156 a ``ConfigClass`` attribute.
157
158 Notes
159 -----
160 If ``ConfigClass`` is provided then the ``target`` configurable is
161 wrapped in a new object that forwards function calls to it. Otherwise
162 the original ``target`` is stored.
163 """
164 if name in self._dict:
165 raise RuntimeError("An item with name %r already exists" % name)
166 if ConfigClass is None:
167 wrapper = target
168 else:
169 wrapper = ConfigurableWrapper(target, ConfigClass)
170 if not issubclass(wrapper.ConfigClass, self._configBaseType):
171 raise TypeError(
172 "ConfigClass=%s is not a subclass of %r"
173 % (_typeStr(wrapper.ConfigClass), _typeStr(self._configBaseType))
174 )
175 self._dict[name] = wrapper
176
177 def __getitem__(self, key):
178 return self._dict[key]
179
180 def __len__(self):
181 return len(self._dict)
182
183 def __iter__(self):
184 return iter(self._dict)
185
186 def __contains__(self, key):
187 return key in self._dict
188
189 def makeField(self, doc, default=None, optional=False, multi=False, on_none=None):
190 """Create a `RegistryField` configuration field from this registry.
191
192 Parameters
193 ----------
194 doc : `str`
195 A description of the field.
196 default : object, optional
197 The default target for the field.
198 optional : `bool`, optional
199 When `False`, `lsst.pex.config.Config.validate` fails if the
200 field's value is `None`.
201 multi : `bool`, optional
202 A flag to allow multiple selections in the `RegistryField` if
203 `True`.
204 on_none : `Callable`, optional
205 A callable that should be invoked when ``apply`` is called but the
206 selected name or names is `None`. Will be passed the field
207 attribute proxy (`RegistryInstanceDict`) and then all positional
208 and keyword arguments passed to ``apply``.
209
210 Returns
211 -------
212 field : `lsst.pex.config.RegistryField`
213 `~lsst.pex.config.RegistryField` Configuration field.
214 """
215 return RegistryField(doc, self, default, optional, multi, on_none=on_none)
216
217
218class RegistryAdaptor(collections.abc.Mapping):
219 """Private class that makes a `Registry` behave like the thing a
220 `~lsst.pex.config.ConfigChoiceField` expects.
221
222 Parameters
223 ----------
224 registry : `Registry`
225 `Registry` instance.
226 """
227
228 def __init__(self, registry):
229 self.registry = registry
230
231 def __getitem__(self, k):
232 return self.registry[k].ConfigClass
233
234 def __iter__(self):
235 return iter(self.registry)
236
237 def __len__(self):
238 return len(self.registry)
239
240 def __contains__(self, k):
241 return k in self.registry
242
243
245 """Dictionary of instantiated configs, used to populate a `RegistryField`.
246
247 Parameters
248 ----------
249 config : `lsst.pex.config.Config`
250 Configuration instance.
251 field : `RegistryField`
252 Configuration field.
253 """
254
255 def __init__(self, config, field):
256 ConfigInstanceDict.__init__(self, config, field)
257 self.registry = field.registry
258
259 def _getTarget(self):
260 if self._field_field.multi:
262 self._field_field, self._config_config, "Multi-selection field has no attribute 'target'"
263 )
264 return self.typestypes.registry[self._selection_selection]
265
266 target = property(_getTarget)
267
268 def _getTargets(self):
269 if not self._field_field.multi:
271 self._field_field, self._config_config, "Single-selection field has no attribute 'targets'"
272 )
273 return [self.typestypes.registry[c] for c in self._selection_selection]
274
275 targets = property(_getTargets)
276
277 def apply(self, *args, **kwargs):
278 """Call the active target(s) with the active config as a keyword arg.
279
280 Parameters
281 ----------
282 *args, **kwargs : `~typing.Any
283 Additional arguments will be passed on to the configurable
284 target(s).
285
286 Returns
287 -------
288 result
289 If this is a single-selection field, the return value from calling
290 the target. If this is a multi-selection field, a list thereof.
291 """
292 if self.active is None:
293 if self._field_field._on_none is not None:
294 return self._field_field._on_none(self, *args, **kwargs)
295 msg = "No selection has been made. Options: %s" % " ".join(self.typestypes.registry.keys())
297 return self.apply_with(self._selection_selection, *args, **kwargs)
298
299 def apply_with(self, selection, *args, **kwargs):
300 """Call named target(s) with the corresponding config as a keyword
301 arg.
302
303 Parameters
304 ----------
305 selection : `str` or `~collections.abc.Iterable` [ `str` ]
306 Name or names of targets, depending on whether ``multi=True``.
307 *args, **kwargs
308 Additional arguments will be passed on to the configurable
309 target(s).
310
311 Returns
312 -------
313 result
314 If this is a single-selection field, the return value from calling
315 the target. If this is a multi-selection field, a list thereof.
316
317 Notes
318 -----
319 This method ignores the current selection in the ``name`` or ``names``
320 attribute, which is usually not what you want. This method is most
321 useful in ``on_none`` callbacks provided at field construction, which
322 allow a context-dependent default to be used when no selection is
323 configured.
324 """
325 if self._field_field.multi:
326 retvals = []
327 for c in selection:
328 retvals.append(self.typestypes.registry[c](*args, config=self[c], **kwargs))
329 return retvals
330 else:
331 return self.typestypes.registry[selection](*args, config=self[selection], **kwargs)
332
333 def __setattr__(self, attr, value):
334 if attr == "registry":
335 object.__setattr__(self, attr, value)
336 else:
337 ConfigInstanceDict.__setattr__(self, attr, value)
338
339
341 """A configuration field whose options are defined in a `Registry`.
342
343 Parameters
344 ----------
345 doc : `str`
346 A description of the field.
347 registry : `Registry`
348 The registry that contains this field.
349 default : `str`, optional
350 The default target key.
351 optional : `bool`, optional
352 When `False`, `lsst.pex.config.Config.validate` fails if the field's
353 value is `None`.
354 multi : `bool`, optional
355 If `True`, the field allows multiple selections. The default is
356 `False`.
357 on_none : `Callable`, optional
358 A callable that should be invoked when ``apply`` is called but the
359 selected name or names is `None`. Will be passed the field attribute
360 proxy (`RegistryInstanceDict`) and then all positional and keyword
361 arguments passed to ``apply``.
362
363 See Also
364 --------
365 ChoiceField
366 ConfigChoiceField
367 ConfigDictField
368 ConfigField
369 ConfigurableField
370 DictField
371 Field
372 ListField
373 RangeField
374 """
375
376 instanceDictClass = RegistryInstanceDict
377 """Class used to hold configurable instances in the field.
378 """
379
380 def __init__(self, doc, registry, default=None, optional=False, multi=False, on_none=None):
381 types = RegistryAdaptor(registry)
382 self.registry = registry
383 self._on_none = on_none
384 ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
385
386 def __deepcopy__(self, memo):
387 """Customize deep-copying, want a reference to the original registry.
388
389 WARNING: this must be overridden by subclasses if they change the
390 constructor signature!
391 """
392 other = type(self)(
393 doc=self.doc,
394 registry=self.registry,
395 default=copy.deepcopy(self.default),
396 optional=self.optional,
397 multi=self.multi,
398 on_none=self._on_none,
399 )
400 other.source = self.source
401 return other
402
403
404def makeRegistry(doc, configBaseType=Config):
405 """Create a `Registry`.
406
407 Parameters
408 ----------
409 doc : `str`
410 Docstring for the created `Registry` (this is set as the ``__doc__``
411 attribute of the `Registry` instance.
412 configBaseType : `lsst.pex.config.Config`-type
413 Base type of config classes in the `Registry`.
414
415 Returns
416 -------
417 registry : `Registry`
418 Registry with ``__doc__`` and ``_configBaseType`` attributes
419 set.
420 """
421 cls = type("Registry", (Registry,), {"__doc__": doc})
422 return cls(configBaseType=configBaseType)
423
424
425def registerConfigurable(name, registry, ConfigClass=None):
426 """Add a class as a configurable in a `Registry` instance.
427
428 Parameters
429 ----------
430 name : `str`
431 Name of the target (the decorated class) in the ``registry``.
432 registry : `Registry`
433 The `Registry` instance that the decorated class is added to.
434 ConfigClass : `lsst.pex.config.Config`-type, optional
435 Config class associated with the configurable. If `None`, the class's
436 ``ConfigClass`` attribute is used instead.
437
438 See Also
439 --------
440 registerConfig
441
442 Notes
443 -----
444 Internally, this decorator runs `Registry.register`.
445 """
446
447 def decorate(cls):
448 registry.register(name, target=cls, ConfigClass=ConfigClass)
449 return cls
450
451 return decorate
452
453
454def registerConfig(name, registry, target):
455 """Add a class as a ``ConfigClass`` in a `Registry` and
456 associate it with the given configurable.
457
458 Parameters
459 ----------
460 name : `str`
461 Name of the ``target`` in the ``registry``.
462 registry : `Registry`
463 The registry containing the ``target``.
464 target : obj
465 A configurable type, such as a subclass of `lsst.pipe.base.Task`.
466
467 See Also
468 --------
469 registerConfigurable
470
471 Notes
472 -----
473 Internally, this decorator runs `Registry.register`.
474 """
475
476 def decorate(cls):
477 registry.register(name, target=target, ConfigClass=cls)
478 return cls
479
480 return decorate
__init__(self, target, ConfigClass)
Definition registry.py:51
__init__(self, doc, registry, default=None, optional=False, multi=False, on_none=None)
Definition registry.py:380
makeField(self, doc, default=None, optional=False, multi=False, on_none=None)
Definition registry.py:189
__init__(self, configBaseType=Config)
Definition registry.py:123
register(self, name, target, ConfigClass=None)
Definition registry.py:134
apply_with(self, selection, *args, **kwargs)
Definition registry.py:299
registerConfigurable(name, registry, ConfigClass=None)
Definition registry.py:425
registerConfig(name, registry, target)
Definition registry.py:454
makeRegistry(doc, configBaseType=Config)
Definition registry.py:404