Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/utils/ipstruct.py: 26%

76 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1# encoding: utf-8 

2"""A dict subclass that supports attribute style access. 

3 

4Authors: 

5 

6* Fernando Perez (original) 

7* Brian Granger (refactoring to a dict subclass) 

8""" 

9 

10#----------------------------------------------------------------------------- 

11# Copyright (C) 2008-2011 The IPython Development Team 

12# 

13# Distributed under the terms of the BSD License. The full license is in 

14# the file COPYING, distributed as part of this software. 

15#----------------------------------------------------------------------------- 

16 

17#----------------------------------------------------------------------------- 

18# Imports 

19#----------------------------------------------------------------------------- 

20 

21__all__ = ['Struct'] 

22 

23#----------------------------------------------------------------------------- 

24# Code 

25#----------------------------------------------------------------------------- 

26 

27 

28class Struct(dict): 

29 """A dict subclass with attribute style access. 

30 

31 This dict subclass has a a few extra features: 

32 

33 * Attribute style access. 

34 * Protection of class members (like keys, items) when using attribute 

35 style access. 

36 * The ability to restrict assignment to only existing keys. 

37 * Intelligent merging. 

38 * Overloaded operators. 

39 """ 

40 _allownew = True 

41 def __init__(self, *args, **kw): 

42 """Initialize with a dictionary, another Struct, or data. 

43 

44 Parameters 

45 ---------- 

46 *args : dict, Struct 

47 Initialize with one dict or Struct 

48 **kw : dict 

49 Initialize with key, value pairs. 

50 

51 Examples 

52 -------- 

53 >>> s = Struct(a=10,b=30) 

54 >>> s.a 

55 10 

56 >>> s.b 

57 30 

58 >>> s2 = Struct(s,c=30) 

59 >>> sorted(s2.keys()) 

60 ['a', 'b', 'c'] 

61 """ 

62 object.__setattr__(self, '_allownew', True) 

63 dict.__init__(self, *args, **kw) 

64 

65 def __setitem__(self, key, value): 

66 """Set an item with check for allownew. 

67 

68 Examples 

69 -------- 

70 >>> s = Struct() 

71 >>> s['a'] = 10 

72 >>> s.allow_new_attr(False) 

73 >>> s['a'] = 10 

74 >>> s['a'] 

75 10 

76 >>> try: 

77 ... s['b'] = 20 

78 ... except KeyError: 

79 ... print('this is not allowed') 

80 ... 

81 this is not allowed 

82 """ 

83 if not self._allownew and key not in self: 

84 raise KeyError( 

85 "can't create new attribute %s when allow_new_attr(False)" % key) 

86 dict.__setitem__(self, key, value) 

87 

88 def __setattr__(self, key, value): 

89 """Set an attr with protection of class members. 

90 

91 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to 

92 :exc:`AttributeError`. 

93 

94 Examples 

95 -------- 

96 >>> s = Struct() 

97 >>> s.a = 10 

98 >>> s.a 

99 10 

100 >>> try: 

101 ... s.get = 10 

102 ... except AttributeError: 

103 ... print("you can't set a class member") 

104 ... 

105 you can't set a class member 

106 """ 

107 # If key is an str it might be a class member or instance var 

108 if isinstance(key, str): 

109 # I can't simply call hasattr here because it calls getattr, which 

110 # calls self.__getattr__, which returns True for keys in 

111 # self._data. But I only want keys in the class and in 

112 # self.__dict__ 

113 if key in self.__dict__ or hasattr(Struct, key): 

114 raise AttributeError( 

115 'attr %s is a protected member of class Struct.' % key 

116 ) 

117 try: 

118 self.__setitem__(key, value) 

119 except KeyError as e: 

120 raise AttributeError(e) from e 

121 

122 def __getattr__(self, key): 

123 """Get an attr by calling :meth:`dict.__getitem__`. 

124 

125 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to 

126 :exc:`AttributeError`. 

127 

128 Examples 

129 -------- 

130 >>> s = Struct(a=10) 

131 >>> s.a 

132 10 

133 >>> type(s.get) 

134 <...method'> 

135 >>> try: 

136 ... s.b 

137 ... except AttributeError: 

138 ... print("I don't have that key") 

139 ... 

140 I don't have that key 

141 """ 

142 try: 

143 result = self[key] 

144 except KeyError as e: 

145 raise AttributeError(key) from e 

146 else: 

147 return result 

148 

149 def __iadd__(self, other): 

150 """s += s2 is a shorthand for s.merge(s2). 

151 

152 Examples 

153 -------- 

154 >>> s = Struct(a=10,b=30) 

155 >>> s2 = Struct(a=20,c=40) 

156 >>> s += s2 

157 >>> sorted(s.keys()) 

158 ['a', 'b', 'c'] 

159 """ 

160 self.merge(other) 

161 return self 

162 

163 def __add__(self,other): 

164 """s + s2 -> New Struct made from s.merge(s2). 

165 

166 Examples 

167 -------- 

168 >>> s1 = Struct(a=10,b=30) 

169 >>> s2 = Struct(a=20,c=40) 

170 >>> s = s1 + s2 

171 >>> sorted(s.keys()) 

172 ['a', 'b', 'c'] 

173 """ 

174 sout = self.copy() 

175 sout.merge(other) 

176 return sout 

177 

178 def __sub__(self,other): 

179 """s1 - s2 -> remove keys in s2 from s1. 

180 

181 Examples 

182 -------- 

183 >>> s1 = Struct(a=10,b=30) 

184 >>> s2 = Struct(a=40) 

185 >>> s = s1 - s2 

186 >>> s 

187 {'b': 30} 

188 """ 

189 sout = self.copy() 

190 sout -= other 

191 return sout 

192 

193 def __isub__(self,other): 

194 """Inplace remove keys from self that are in other. 

195 

196 Examples 

197 -------- 

198 >>> s1 = Struct(a=10,b=30) 

199 >>> s2 = Struct(a=40) 

200 >>> s1 -= s2 

201 >>> s1 

202 {'b': 30} 

203 """ 

204 for k in other.keys(): 

205 if k in self: 

206 del self[k] 

207 return self 

208 

209 def __dict_invert(self, data): 

210 """Helper function for merge. 

211 

212 Takes a dictionary whose values are lists and returns a dict with 

213 the elements of each list as keys and the original keys as values. 

214 """ 

215 outdict = {} 

216 for k,lst in data.items(): 

217 if isinstance(lst, str): 

218 lst = lst.split() 

219 for entry in lst: 

220 outdict[entry] = k 

221 return outdict 

222 

223 def dict(self): 

224 return self 

225 

226 def copy(self): 

227 """Return a copy as a Struct. 

228 

229 Examples 

230 -------- 

231 >>> s = Struct(a=10,b=30) 

232 >>> s2 = s.copy() 

233 >>> type(s2) is Struct 

234 True 

235 """ 

236 return Struct(dict.copy(self)) 

237 

238 def hasattr(self, key): 

239 """hasattr function available as a method. 

240 

241 Implemented like has_key. 

242 

243 Examples 

244 -------- 

245 >>> s = Struct(a=10) 

246 >>> s.hasattr('a') 

247 True 

248 >>> s.hasattr('b') 

249 False 

250 >>> s.hasattr('get') 

251 False 

252 """ 

253 return key in self 

254 

255 def allow_new_attr(self, allow = True): 

256 """Set whether new attributes can be created in this Struct. 

257 

258 This can be used to catch typos by verifying that the attribute user 

259 tries to change already exists in this Struct. 

260 """ 

261 object.__setattr__(self, '_allownew', allow) 

262 

263 def merge(self, __loc_data__=None, __conflict_solve=None, **kw): 

264 """Merge two Structs with customizable conflict resolution. 

265 

266 This is similar to :meth:`update`, but much more flexible. First, a 

267 dict is made from data+key=value pairs. When merging this dict with 

268 the Struct S, the optional dictionary 'conflict' is used to decide 

269 what to do. 

270 

271 If conflict is not given, the default behavior is to preserve any keys 

272 with their current value (the opposite of the :meth:`update` method's 

273 behavior). 

274 

275 Parameters 

276 ---------- 

277 __loc_data__ : dict, Struct 

278 The data to merge into self 

279 __conflict_solve : dict 

280 The conflict policy dict. The keys are binary functions used to 

281 resolve the conflict and the values are lists of strings naming 

282 the keys the conflict resolution function applies to. Instead of 

283 a list of strings a space separated string can be used, like 

284 'a b c'. 

285 **kw : dict 

286 Additional key, value pairs to merge in 

287 

288 Notes 

289 ----- 

290 The `__conflict_solve` dict is a dictionary of binary functions which will be used to 

291 solve key conflicts. Here is an example:: 

292 

293 __conflict_solve = dict( 

294 func1=['a','b','c'], 

295 func2=['d','e'] 

296 ) 

297 

298 In this case, the function :func:`func1` will be used to resolve 

299 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for 

300 keys 'd' and 'e'. This could also be written as:: 

301 

302 __conflict_solve = dict(func1='a b c',func2='d e') 

303 

304 These functions will be called for each key they apply to with the 

305 form:: 

306 

307 func1(self['a'], other['a']) 

308 

309 The return value is used as the final merged value. 

310 

311 As a convenience, merge() provides five (the most commonly needed) 

312 pre-defined policies: preserve, update, add, add_flip and add_s. The 

313 easiest explanation is their implementation:: 

314 

315 preserve = lambda old,new: old 

316 update = lambda old,new: new 

317 add = lambda old,new: old + new 

318 add_flip = lambda old,new: new + old # note change of order! 

319 add_s = lambda old,new: old + ' ' + new # only for str! 

320 

321 You can use those four words (as strings) as keys instead 

322 of defining them as functions, and the merge method will substitute 

323 the appropriate functions for you. 

324 

325 For more complicated conflict resolution policies, you still need to 

326 construct your own functions. 

327 

328 Examples 

329 -------- 

330 This show the default policy: 

331 

332 >>> s = Struct(a=10,b=30) 

333 >>> s2 = Struct(a=20,c=40) 

334 >>> s.merge(s2) 

335 >>> sorted(s.items()) 

336 [('a', 10), ('b', 30), ('c', 40)] 

337 

338 Now, show how to specify a conflict dict: 

339 

340 >>> s = Struct(a=10,b=30) 

341 >>> s2 = Struct(a=20,b=40) 

342 >>> conflict = {'update':'a','add':'b'} 

343 >>> s.merge(s2,conflict) 

344 >>> sorted(s.items()) 

345 [('a', 20), ('b', 70)] 

346 """ 

347 

348 data_dict = dict(__loc_data__,**kw) 

349 

350 # policies for conflict resolution: two argument functions which return 

351 # the value that will go in the new struct 

352 preserve = lambda old,new: old 

353 update = lambda old,new: new 

354 add = lambda old,new: old + new 

355 add_flip = lambda old,new: new + old # note change of order! 

356 add_s = lambda old,new: old + ' ' + new 

357 

358 # default policy is to keep current keys when there's a conflict 

359 conflict_solve = dict.fromkeys(self, preserve) 

360 

361 # the conflict_solve dictionary is given by the user 'inverted': we 

362 # need a name-function mapping, it comes as a function -> names 

363 # dict. Make a local copy (b/c we'll make changes), replace user 

364 # strings for the three builtin policies and invert it. 

365 if __conflict_solve: 

366 inv_conflict_solve_user = __conflict_solve.copy() 

367 for name, func in [('preserve',preserve), ('update',update), 

368 ('add',add), ('add_flip',add_flip), 

369 ('add_s',add_s)]: 

370 if name in inv_conflict_solve_user.keys(): 

371 inv_conflict_solve_user[func] = inv_conflict_solve_user[name] 

372 del inv_conflict_solve_user[name] 

373 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user)) 

374 for key in data_dict: 

375 if key not in self: 

376 self[key] = data_dict[key] 

377 else: 

378 self[key] = conflict_solve[key](self[key],data_dict[key]) 

379