Hide keyboard shortcuts

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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("MappingFactory", ) 

25 

26from typing import ( 

27 Any, 

28 Dict, 

29 Iterable, 

30 List, 

31 Set, 

32 Tuple, 

33 Type, 

34 Union, 

35) 

36 

37from .utils import getClassOf 

38from .configSupport import LookupKey 

39 

40 

41class MappingFactory: 

42 """ 

43 Register the mapping of some key to a python type and retrieve instances. 

44 

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. 

49 

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. 

56 

57 """ 

58 

59 def __init__(self, refType: Type): 

60 self._registry: Dict[LookupKey, Union[str, Type]] = {} 

61 self.refType = refType 

62 

63 def __contains__(self, key: Any) -> bool: 

64 """Indicates whether the supplied key is present in the factory. 

65 

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. 

71 

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 

79 

80 def getLookupKeys(self) -> Set[LookupKey]: 

81 """Retrieve the look up keys for all the registry entries. 

82 

83 Returns 

84 ------- 

85 keys : `set` of `LookupKey` 

86 The keys available for matching in the registry. 

87 """ 

88 return set(self._registry) 

89 

90 def getClassFromRegistryWithMatch(self, targetClasses: Iterable[Any]) -> Tuple[LookupKey, Type]: 

91 """Get the class stored in the registry along with 

92 the matching key. 

93 

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. 

99 

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. 

107 

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) 

127 

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}") 

132 

133 def getClassFromRegistry(self, targetClasses: Iterable[Any], *args: Any, **kwargs: Any) -> Type: 

134 """Get the matching class stored in the registry. 

135 

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. 

141 

142 Returns 

143 ------- 

144 cls : `type` 

145 Class stored in registry associated with the first 

146 matching target class. 

147 

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 

156 

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. 

161 

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. 

171 

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. 

179 

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) 

188 

189 def getFromRegistry(self, targetClasses: Iterable[Any], *args: Any, **kwargs: Any) -> Any: 

190 """Get a new instance of the object stored in the registry. 

191 

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. 

201 

202 Returns 

203 ------- 

204 instance : `object` 

205 Instance of class stored in registry associated with the first 

206 matching target class. 

207 

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 

216 

217 def placeInRegistry(self, registryKey: Any, typeName: Union[str, Type], overwrite: bool = False) -> None: 

218 """Register a class name with the associated type. 

219 

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`. 

230 

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 

243 

244 raise KeyError("Item with key {} already registered with different value" 

245 " ({} != {})".format(key, self._registry[key], typeName)) 

246 

247 self._registry[key] = typeName 

248 

249 @staticmethod 

250 def _getNameKey(typeOrName: Any) -> LookupKey: 

251 """Extract name of supplied object as string or entity suitable for 

252 using as key. 

253 

254 Parameters 

255 ---------- 

256 typeOrName : `LookupKey, `str` or object supporting ``name`` attribute. 

257 Item from which to extract a name. 

258 

259 Returns 

260 ------- 

261 name : `LookupKey` 

262 Extracted name as a string or 

263 """ 

264 if isinstance(typeOrName, LookupKey): 

265 return typeOrName 

266 

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") 

273 

274 return LookupKey(name=name)