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

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/>.
22__all__ = ("MappingFactory", )
24from .utils import getClassOf
25from .configSupport import LookupKey
28class MappingFactory:
29 """
30 Register the mapping of some key to a python type and retrieve instances.
32 Enables instances of these classes to be retrieved from the factory later.
33 The class can be specified as an object, class or string.
34 If the key is an object it is converted to a string by accessing
35 a ``name`` attribute.
37 Parameters
38 ----------
39 refType : `type`
40 Python reference `type` to use to ensure that items stored in the
41 registry create instance objects of the correct class. Subclasses
42 of this type are allowed. Using `None` disables the check.
44 """
46 def __init__(self, refType):
47 self._registry = {}
48 self.refType = refType
50 def __contains__(self, key):
51 """Indicates whether the supplied key is present in the factory.
53 Parameters
54 ----------
55 key : `LookupKey`, `str` or objects with ``name`` attribute
56 Key to use to lookup whether a corresponding element exists
57 in this factory.
59 Returns
60 -------
61 in : `bool`
62 `True` if the supplied key is present in the factory.
63 """
64 key = self._getNameKey(key)
65 return key in self._registry
67 def getLookupKeys(self):
68 """Retrieve the look up keys for all the registry entries.
70 Returns
71 -------
72 keys : `set` of `LookupKey`
73 The keys available for matching in the registry.
74 """
75 return set(self._registry)
77 def getClassFromRegistryWithMatch(self, targetClasses):
78 """Get the class stored in the registry along with
79 the matching key.
81 Parameters
82 ----------
83 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
84 Each item is tested in turn until a match is found in the registry.
85 Items with `None` value are skipped.
87 Returns
88 -------
89 matchKey : `LookupKey`
90 The key that resulted in the successful match.
91 cls : `type`
92 Class stored in registry associated with the first
93 matching target class.
95 Raises
96 ------
97 KeyError
98 Raised if none of the supplied target classes match an item in the
99 registry.
100 """
101 attempts = []
102 for t in (targetClasses):
103 if t is None:
104 attempts.append(t)
105 else:
106 key = self._getNameKey(t)
107 attempts.append(key)
108 try:
109 typeName = self._registry[key]
110 except KeyError:
111 pass
112 else:
113 return key, getClassOf(typeName)
115 # Convert list to a string for error reporting
116 msg = ", ".join(str(k) for k in attempts)
117 plural = "" if len(attempts) == 1 else "s"
118 raise KeyError(f"Unable to find item in registry with key{plural}: {msg}")
120 def getClassFromRegistry(self, targetClasses, *args, **kwargs):
121 """Get the matching class stored in the registry.
123 Parameters
124 ----------
125 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
126 Each item is tested in turn until a match is found in the registry.
127 Items with `None` value are skipped.
129 Returns
130 -------
131 cls : `type`
132 Class stored in registry associated with the first
133 matching target class.
135 Raises
136 ------
137 KeyError
138 Raised if none of the supplied target classes match an item in the
139 registry.
140 """
141 _, cls = self.getClassFromRegistryWithMatch(targetClasses)
142 return cls
144 def getFromRegistryWithMatch(self, targetClasses, *args, **kwargs):
145 """Get a new instance of the object stored in the registry along with
146 the matching key.
148 Parameters
149 ----------
150 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
151 Each item is tested in turn until a match is found in the registry.
152 Items with `None` value are skipped.
153 args : `tuple`
154 Positional arguments to use pass to the object constructor.
155 kwargs : `dict`
156 Keyword arguments to pass to object constructor.
158 Returns
159 -------
160 matchKey : `LookupKey`
161 The key that resulted in the successful match.
162 instance : `object`
163 Instance of class stored in registry associated with the first
164 matching target class.
166 Raises
167 ------
168 KeyError
169 Raised if none of the supplied target classes match an item in the
170 registry.
171 """
172 key, cls = self.getClassFromRegistryWithMatch(targetClasses)
173 return key, cls(*args, **kwargs)
175 def getFromRegistry(self, targetClasses, *args, **kwargs):
176 """Get a new instance of the object stored in the registry.
178 Parameters
179 ----------
180 targetClasses : `LookupKey`, `str` or objects with ``name`` attribute
181 Each item is tested in turn until a match is found in the registry.
182 Items with `None` value are skipped.
183 args : `tuple`
184 Positional arguments to use pass to the object constructor.
185 kwargs : `dict`
186 Keyword arguments to pass to object constructor.
188 Returns
189 -------
190 instance : `object`
191 Instance of class stored in registry associated with the first
192 matching target class.
194 Raises
195 ------
196 KeyError
197 Raised if none of the supplied target classes match an item in the
198 registry.
199 """
200 _, instance = self.getFromRegistryWithMatch(targetClasses, *args, **kwargs)
201 return instance
203 def placeInRegistry(self, registryKey, typeName, overwrite=False):
204 """Register a class name with the associated type.
206 Parameters
207 ----------
208 registryKey : `LookupKey`, `str` or object with ``name`` attribute.
209 Item to associate with the provided type.
210 typeName : `str` or Python type
211 Identifies a class to associate with the provided key.
212 overwrite : `bool`, optional
213 If `True`, an existing entry will be overwritten. This option
214 is expected to be used to simplify test suites.
215 Default is `False`.
217 Raises
218 ------
219 KeyError
220 Raised if item is already registered and has different value and
221 ``overwrite`` is `False`.
222 """
223 key = self._getNameKey(registryKey)
224 if key in self._registry and not overwrite:
225 # Compare the class strings since dynamic classes can be the
226 # same thing but be different.
227 if str(self._registry[key]) == str(typeName):
228 return
230 raise KeyError("Item with key {} already registered with different value"
231 " ({} != {})".format(key, self._registry[key], typeName))
233 self._registry[key] = typeName
235 @staticmethod
236 def _getNameKey(typeOrName):
237 """Extract name of supplied object as string or entity suitable for
238 using as key.
240 Parameters
241 ----------
242 typeOrName : `LookupKey, `str` or object supporting ``name`` attribute.
243 Item from which to extract a name.
245 Returns
246 -------
247 name : `LookupKey`
248 Extracted name as a string or
249 """
250 if isinstance(typeOrName, LookupKey):
251 return typeOrName
253 if isinstance(typeOrName, str):
254 name = typeOrName
255 elif hasattr(typeOrName, "name"):
256 name = typeOrName.name
257 else:
258 raise ValueError("Cannot extract name from type")
260 return LookupKey(name=name)