Coverage for python/lsst/daf/butler/core/mappingFactory.py : 21%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of daf_butler.
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/>.
22from __future__ import annotations
24__all__ = ("MappingFactory", )
26from typing import (
27 Any,
28 Dict,
29 Iterable,
30 List,
31 Set,
32 Tuple,
33 Type,
34 Union,
35)
37from .utils import getClassOf
38from .configSupport import LookupKey
41class MappingFactory:
42 """
43 Register the mapping of some key to a python type and retrieve instances.
45 Enables instances of these classes to be retrieved from the factory later.
46 The class can be specified as an object, class or string.
47 If the key is an object it is converted to a string by accessing
48 a ``name`` attribute.
50 Parameters
51 ----------
52 refType : `type`
53 Python reference `type` to use to ensure that items stored in the
54 registry create instance objects of the correct class. Subclasses
55 of this type are allowed. Using `None` disables the check.
57 """
59 def __init__(self, refType: Type):
60 self._registry: Dict[LookupKey, Union[str, Type]] = {}
61 self.refType = refType
63 def __contains__(self, key: Any) -> bool:
64 """Indicates whether the supplied key is present in the factory.
66 Parameters
67 ----------
68 key : `LookupKey`, `str` or objects with ``name`` attribute
69 Key to use to lookup whether a corresponding element exists
70 in this factory.
72 Returns
73 -------
74 in : `bool`
75 `True` if the supplied key is present in the factory.
76 """
77 key = self._getNameKey(key)
78 return key in self._registry
80 def getLookupKeys(self) -> Set[LookupKey]:
81 """Retrieve the look up keys for all the registry entries.
83 Returns
84 -------
85 keys : `set` of `LookupKey`
86 The keys available for matching in the registry.
87 """
88 return set(self._registry)
90 def getClassFromRegistryWithMatch(self, targetClasses: Iterable[Any]) -> Tuple[LookupKey, Type]:
91 """Get the class stored in the registry along with
92 the matching key.
94 Parameters
95 ----------
96 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
97 Each item is tested in turn until a match is found in the registry.
98 Items with `None` value are skipped.
100 Returns
101 -------
102 matchKey : `LookupKey`
103 The key that resulted in the successful match.
104 cls : `type`
105 Class stored in registry associated with the first
106 matching target class.
108 Raises
109 ------
110 KeyError
111 Raised if none of the supplied target classes match an item in the
112 registry.
113 """
114 attempts: List[Any] = []
115 for t in (targetClasses):
116 if t is None:
117 attempts.append(t)
118 else:
119 key = self._getNameKey(t)
120 attempts.append(key)
121 try:
122 typeName = self._registry[key]
123 except KeyError:
124 pass
125 else:
126 return key, getClassOf(typeName)
128 # Convert list to a string for error reporting
129 msg = ", ".join(str(k) for k in attempts)
130 plural = "" if len(attempts) == 1 else "s"
131 raise KeyError(f"Unable to find item in registry with key{plural}: {msg}")
133 def getClassFromRegistry(self, targetClasses: Iterable[Any], *args: Any, **kwargs: Any) -> Type:
134 """Get the matching class stored in the registry.
136 Parameters
137 ----------
138 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
139 Each item is tested in turn until a match is found in the registry.
140 Items with `None` value are skipped.
142 Returns
143 -------
144 cls : `type`
145 Class stored in registry associated with the first
146 matching target class.
148 Raises
149 ------
150 KeyError
151 Raised if none of the supplied target classes match an item in the
152 registry.
153 """
154 _, cls = self.getClassFromRegistryWithMatch(targetClasses)
155 return cls
157 def getFromRegistryWithMatch(self, targetClasses: Iterable[Any], *args: Any,
158 **kwargs: Any) -> Tuple[LookupKey, Any]:
159 """Get a new instance of the object stored in the registry along with
160 the matching key.
162 Parameters
163 ----------
164 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
165 Each item is tested in turn until a match is found in the registry.
166 Items with `None` value are skipped.
167 args : `tuple`
168 Positional arguments to use pass to the object constructor.
169 kwargs : `dict`
170 Keyword arguments to pass to object constructor.
172 Returns
173 -------
174 matchKey : `LookupKey`
175 The key that resulted in the successful match.
176 instance : `object`
177 Instance of class stored in registry associated with the first
178 matching target class.
180 Raises
181 ------
182 KeyError
183 Raised if none of the supplied target classes match an item in the
184 registry.
185 """
186 key, cls = self.getClassFromRegistryWithMatch(targetClasses)
187 return key, cls(*args, **kwargs)
189 def getFromRegistry(self, targetClasses: Iterable[Any], *args: Any, **kwargs: Any) -> Any:
190 """Get a new instance of the object stored in the registry.
192 Parameters
193 ----------
194 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
195 Each item is tested in turn until a match is found in the registry.
196 Items with `None` value are skipped.
197 args : `tuple`
198 Positional arguments to use pass to the object constructor.
199 kwargs : `dict`
200 Keyword arguments to pass to object constructor.
202 Returns
203 -------
204 instance : `object`
205 Instance of class stored in registry associated with the first
206 matching target class.
208 Raises
209 ------
210 KeyError
211 Raised if none of the supplied target classes match an item in the
212 registry.
213 """
214 _, instance = self.getFromRegistryWithMatch(targetClasses, *args, **kwargs)
215 return instance
217 def placeInRegistry(self, registryKey: Any, typeName: Union[str, Type], overwrite: bool = False) -> None:
218 """Register a class name with the associated type.
220 Parameters
221 ----------
222 registryKey : `LookupKey`, `str` or object with ``name`` attribute.
223 Item to associate with the provided type.
224 typeName : `str` or Python type
225 Identifies a class to associate with the provided key.
226 overwrite : `bool`, optional
227 If `True`, an existing entry will be overwritten. This option
228 is expected to be used to simplify test suites.
229 Default is `False`.
231 Raises
232 ------
233 KeyError
234 Raised if item is already registered and has different value and
235 ``overwrite`` is `False`.
236 """
237 key = self._getNameKey(registryKey)
238 if key in self._registry and not overwrite:
239 # Compare the class strings since dynamic classes can be the
240 # same thing but be different.
241 if str(self._registry[key]) == str(typeName):
242 return
244 raise KeyError("Item with key {} already registered with different value"
245 " ({} != {})".format(key, self._registry[key], typeName))
247 self._registry[key] = typeName
249 @staticmethod
250 def _getNameKey(typeOrName: Any) -> LookupKey:
251 """Extract name of supplied object as string or entity suitable for
252 using as key.
254 Parameters
255 ----------
256 typeOrName : `LookupKey, `str` or object supporting ``name`` attribute.
257 Item from which to extract a name.
259 Returns
260 -------
261 name : `LookupKey`
262 Extracted name as a string or
263 """
264 if isinstance(typeOrName, LookupKey):
265 return typeOrName
267 if isinstance(typeOrName, str):
268 name = typeOrName
269 elif hasattr(typeOrName, "name"):
270 name = typeOrName.name
271 else:
272 raise ValueError("Cannot extract name from type")
274 return LookupKey(name=name)