LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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(f"configBaseType={_typeStr(configBaseType)} must be a subclass of Config")
126 self._configBaseType = configBaseType
127 self._dict = {}
128
129 def register(self, name, target, ConfigClass=None):
130 """Add a new configurable target to the registry.
131
132 Parameters
133 ----------
134 name : `str`
135 Name that the ``target`` is registered under. The target can
136 be accessed later with `dict`-like patterns using ``name`` as
137 the key.
138 target : obj
139 A configurable type, usually a subclass of `lsst.pipe.base.Task`.
140 ConfigClass : `lsst.pex.config.Config`-type, optional
141 A subclass of `lsst.pex.config.Config` used to configure the
142 configurable. If `None` then the configurable's ``ConfigClass``
143 attribute is used.
144
145 Raises
146 ------
147 RuntimeError
148 Raised if an item with ``name`` is already in the registry.
149 AttributeError
150 Raised if ``ConfigClass`` is `None` and ``target`` does not have
151 a ``ConfigClass`` attribute.
152
153 Notes
154 -----
155 If ``ConfigClass`` is provided then the ``target`` configurable is
156 wrapped in a new object that forwards function calls to it. Otherwise
157 the original ``target`` is stored.
158 """
159 if name in self._dict:
160 raise RuntimeError(f"An item with name {name!r} already exists")
161 if ConfigClass is None:
162 wrapper = target
163 else:
164 wrapper = ConfigurableWrapper(target, ConfigClass)
165 if not issubclass(wrapper.ConfigClass, self._configBaseType):
166 raise TypeError(
167 f"ConfigClass={_typeStr(wrapper.ConfigClass)} is not a subclass of "
168 f"{_typeStr(self._configBaseType)!r}"
169 )
170 self._dict[name] = wrapper
171
172 def __getitem__(self, key):
173 return self._dict[key]
174
175 def __len__(self):
176 return len(self._dict)
177
178 def __iter__(self):
179 return iter(self._dict)
180
181 def __contains__(self, key):
182 return key in self._dict
183
184 def makeField(self, doc, default=None, optional=False, multi=False, on_none=None):
185 """Create a `RegistryField` configuration field from this registry.
186
187 Parameters
188 ----------
189 doc : `str`
190 A description of the field.
191 default : object, optional
192 The default target for the field.
193 optional : `bool`, optional
194 When `False`, `lsst.pex.config.Config.validate` fails if the
195 field's value is `None`.
196 multi : `bool`, optional
197 A flag to allow multiple selections in the `RegistryField` if
198 `True`.
199 on_none : `Callable`, optional
200 A callable that should be invoked when ``apply`` is called but the
201 selected name or names is `None`. Will be passed the field
202 attribute proxy (`RegistryInstanceDict`) and then all positional
203 and keyword arguments passed to ``apply``.
204
205 Returns
206 -------
207 field : `lsst.pex.config.RegistryField`
208 `~lsst.pex.config.RegistryField` Configuration field.
209 """
210 return RegistryField(doc, self, default, optional, multi, on_none=on_none)
211
212
213class RegistryAdaptor(collections.abc.Mapping):
214 """Private class that makes a `Registry` behave like the thing a
215 `~lsst.pex.config.ConfigChoiceField` expects.
216
217 Parameters
218 ----------
219 registry : `Registry`
220 `Registry` instance.
221 """
222
223 def __init__(self, registry):
224 self.registry = registry
225
226 def __getitem__(self, k):
227 return self.registry[k].ConfigClass
228
229 def __iter__(self):
230 return iter(self.registry)
231
232 def __len__(self):
233 return len(self.registry)
234
235 def __contains__(self, k):
236 return k in self.registry
237
238
240 """Dictionary of instantiated configs, used to populate a `RegistryField`.
241
242 Parameters
243 ----------
244 config : `lsst.pex.config.Config`
245 Configuration instance.
246 field : `RegistryField`
247 Configuration field.
248 """
249
250 def __init__(self, config, field):
251 ConfigInstanceDict.__init__(self, config, field)
252 self.registry = field.registry
253
254 def _getTarget(self):
255 if self._field_field.multi:
257 self._field_field, self._config_config, "Multi-selection field has no attribute 'target'"
258 )
259 return self.typestypes.registry[self._selection_selection]
260
261 target = property(_getTarget)
262
263 def _getTargets(self):
264 if not self._field_field.multi:
266 self._field_field, self._config_config, "Single-selection field has no attribute 'targets'"
267 )
268 return [self.typestypes.registry[c] for c in self._selection_selection]
269
270 targets = property(_getTargets)
271
272 def apply(self, *args, **kwargs):
273 """Call the active target(s) with the active config as a keyword arg.
274
275 Parameters
276 ----------
277 *args, **kwargs : `~typing.Any
278 Additional arguments will be passed on to the configurable
279 target(s).
280
281 Returns
282 -------
283 result
284 If this is a single-selection field, the return value from calling
285 the target. If this is a multi-selection field, a list thereof.
286 """
287 if self.active is None:
288 if self._field_field._on_none is not None:
289 return self._field_field._on_none(self, *args, **kwargs)
290 msg = "No selection has been made. Options: {}".format(" ".join(self.typestypes.registry.keys()))
292 return self.apply_with(self._selection_selection, *args, **kwargs)
293
294 def apply_with(self, selection, *args, **kwargs):
295 """Call named target(s) with the corresponding config as a keyword
296 arg.
297
298 Parameters
299 ----------
300 selection : `str` or `~collections.abc.Iterable` [ `str` ]
301 Name or names of targets, depending on whether ``multi=True``.
302 *args, **kwargs
303 Additional arguments will be passed on to the configurable
304 target(s).
305
306 Returns
307 -------
308 result
309 If this is a single-selection field, the return value from calling
310 the target. If this is a multi-selection field, a list thereof.
311
312 Notes
313 -----
314 This method ignores the current selection in the ``name`` or ``names``
315 attribute, which is usually not what you want. This method is most
316 useful in ``on_none`` callbacks provided at field construction, which
317 allow a context-dependent default to be used when no selection is
318 configured.
319 """
320 if self._field_field.multi:
321 retvals = []
322 for c in selection:
323 retvals.append(self.typestypes.registry[c](*args, config=self[c], **kwargs))
324 return retvals
325 else:
326 return self.typestypes.registry[selection](*args, config=self[selection], **kwargs)
327
328 def __setattr__(self, attr, value):
329 if attr == "registry":
330 object.__setattr__(self, attr, value)
331 else:
332 ConfigInstanceDict.__setattr__(self, attr, value)
333
334
336 """A configuration field whose options are defined in a `Registry`.
337
338 Parameters
339 ----------
340 doc : `str`
341 A description of the field.
342 registry : `Registry`
343 The registry that contains this field.
344 default : `str`, optional
345 The default target key.
346 optional : `bool`, optional
347 When `False`, `lsst.pex.config.Config.validate` fails if the field's
348 value is `None`.
349 multi : `bool`, optional
350 If `True`, the field allows multiple selections. The default is
351 `False`.
352 on_none : `Callable`, optional
353 A callable that should be invoked when ``apply`` is called but the
354 selected name or names is `None`. Will be passed the field attribute
355 proxy (`RegistryInstanceDict`) and then all positional and keyword
356 arguments passed to ``apply``.
357
358 See Also
359 --------
360 ChoiceField
361 ConfigChoiceField
362 ConfigDictField
363 ConfigField
364 ConfigurableField
365 DictField
366 Field
367 ListField
368 RangeField
369 """
370
371 instanceDictClass = RegistryInstanceDict
372 """Class used to hold configurable instances in the field.
373 """
374
375 def __init__(self, doc, registry, default=None, optional=False, multi=False, on_none=None):
376 types = RegistryAdaptor(registry)
377 self.registry = registry
378 self._on_none = on_none
379 ConfigChoiceField.__init__(self, doc, types, default, optional, multi)
380
381 def __deepcopy__(self, memo):
382 """Customize deep-copying, want a reference to the original registry.
383
384 WARNING: this must be overridden by subclasses if they change the
385 constructor signature!
386 """
387 other = type(self)(
388 doc=self.doc,
389 registry=self.registry,
390 default=copy.deepcopy(self.default),
391 optional=self.optional,
392 multi=self.multi,
393 on_none=self._on_none,
394 )
395 other.source = self.source
396 return other
397
398
399def makeRegistry(doc, configBaseType=Config):
400 """Create a `Registry`.
401
402 Parameters
403 ----------
404 doc : `str`
405 Docstring for the created `Registry` (this is set as the ``__doc__``
406 attribute of the `Registry` instance.
407 configBaseType : `lsst.pex.config.Config`-type
408 Base type of config classes in the `Registry`.
409
410 Returns
411 -------
412 registry : `Registry`
413 Registry with ``__doc__`` and ``_configBaseType`` attributes
414 set.
415 """
416 cls = type("Registry", (Registry,), {"__doc__": doc})
417 return cls(configBaseType=configBaseType)
418
419
420def registerConfigurable(name, registry, ConfigClass=None):
421 """Add a class as a configurable in a `Registry` instance.
422
423 Parameters
424 ----------
425 name : `str`
426 Name of the target (the decorated class) in the ``registry``.
427 registry : `Registry`
428 The `Registry` instance that the decorated class is added to.
429 ConfigClass : `lsst.pex.config.Config`-type, optional
430 Config class associated with the configurable. If `None`, the class's
431 ``ConfigClass`` attribute is used instead.
432
433 See Also
434 --------
435 registerConfig
436
437 Notes
438 -----
439 Internally, this decorator runs `Registry.register`.
440 """
441
442 def decorate(cls):
443 registry.register(name, target=cls, ConfigClass=ConfigClass)
444 return cls
445
446 return decorate
447
448
449def registerConfig(name, registry, target):
450 """Add a class as a ``ConfigClass`` in a `Registry` and
451 associate it with the given configurable.
452
453 Parameters
454 ----------
455 name : `str`
456 Name of the ``target`` in the ``registry``.
457 registry : `Registry`
458 The registry containing the ``target``.
459 target : obj
460 A configurable type, such as a subclass of `lsst.pipe.base.Task`.
461
462 See Also
463 --------
464 registerConfigurable
465
466 Notes
467 -----
468 Internally, this decorator runs `Registry.register`.
469 """
470
471 def decorate(cls):
472 registry.register(name, target=target, ConfigClass=cls)
473 return cls
474
475 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:375
makeField(self, doc, default=None, optional=False, multi=False, on_none=None)
Definition registry.py:184
__init__(self, configBaseType=Config)
Definition registry.py:123
register(self, name, target, ConfigClass=None)
Definition registry.py:129
apply_with(self, selection, *args, **kwargs)
Definition registry.py:294
registerConfigurable(name, registry, ConfigClass=None)
Definition registry.py:420
registerConfig(name, registry, target)
Definition registry.py:449
makeRegistry(doc, configBaseType=Config)
Definition registry.py:399