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 

22__all__ = ("MappingFactory", ) 

23 

24from .utils import getClassOf 

25from .configSupport import LookupKey 

26 

27 

28class MappingFactory: 

29 """ 

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

31 

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. 

36 

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. 

43 

44 """ 

45 

46 def __init__(self, refType): 

47 self._registry = {} 

48 self.refType = refType 

49 

50 def __contains__(self, key): 

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

52 

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. 

58 

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 

66 

67 def getLookupKeys(self): 

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

69 

70 Returns 

71 ------- 

72 keys : `set` of `LookupKey` 

73 The keys available for matching in the registry. 

74 """ 

75 return set(self._registry) 

76 

77 def getClassFromRegistryWithMatch(self, targetClasses): 

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

79 the matching key. 

80 

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. 

86 

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. 

94 

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) 

114 

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

119 

120 def getClassFromRegistry(self, targetClasses, *args, **kwargs): 

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

122 

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. 

128 

129 Returns 

130 ------- 

131 cls : `type` 

132 Class stored in registry associated with the first 

133 matching target class. 

134 

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 

143 

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. 

147 

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. 

157 

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. 

165 

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) 

174 

175 def getFromRegistry(self, targetClasses, *args, **kwargs): 

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

177 

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. 

187 

188 Returns 

189 ------- 

190 instance : `object` 

191 Instance of class stored in registry associated with the first 

192 matching target class. 

193 

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 

202 

203 def placeInRegistry(self, registryKey, typeName, overwrite=False): 

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

205 

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

216 

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 

229 

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

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

232 

233 self._registry[key] = typeName 

234 

235 @staticmethod 

236 def _getNameKey(typeOrName): 

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

238 using as key. 

239 

240 Parameters 

241 ---------- 

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

243 Item from which to extract a name. 

244 

245 Returns 

246 ------- 

247 name : `LookupKey` 

248 Extracted name as a string or 

249 """ 

250 if isinstance(typeOrName, LookupKey): 

251 return typeOrName 

252 

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

259 

260 return LookupKey(name=name)