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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

78 statements  

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

9from typing import Any 

10 

11#----------------------------------------------------------------------------- 

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

13# 

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

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

16#----------------------------------------------------------------------------- 

17 

18#----------------------------------------------------------------------------- 

19# Imports 

20#----------------------------------------------------------------------------- 

21 

22__all__ = ['Struct'] 

23 

24#----------------------------------------------------------------------------- 

25# Code 

26#----------------------------------------------------------------------------- 

27 

28 

29class Struct(dict): 

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

31 

32 This dict subclass has a a few extra features: 

33 

34 * Attribute style access. 

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

36 style access. 

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

38 * Intelligent merging. 

39 * Overloaded operators. 

40 """ 

41 _allownew = True 

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

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

44 

45 Parameters 

46 ---------- 

47 *args : dict, Struct 

48 Initialize with one dict or Struct 

49 **kw : dict 

50 Initialize with key, value pairs. 

51 

52 Examples 

53 -------- 

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

55 >>> s.a 

56 10 

57 >>> s.b 

58 30 

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

60 >>> sorted(s2.keys()) 

61 ['a', 'b', 'c'] 

62 """ 

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

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

65 

66 def __setitem__(self, key: str, value: Any): 

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

68 

69 Examples 

70 -------- 

71 >>> s = Struct() 

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

73 >>> s.allow_new_attr(False) 

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

75 >>> s['a'] 

76 10 

77 >>> try: 

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

79 ... except KeyError: 

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

81 ... 

82 this is not allowed 

83 """ 

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

85 raise KeyError( 

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

87 dict.__setitem__(self, key, value) 

88 

89 def __setattr__(self, key: str, value: Any): 

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

91 

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

93 :exc:`AttributeError`. 

94 

95 Examples 

96 -------- 

97 >>> s = Struct() 

98 >>> s.a = 10 

99 >>> s.a 

100 10 

101 >>> try: 

102 ... s.get = 10 

103 ... except AttributeError: 

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

105 ... 

106 you can't set a class member 

107 """ 

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

109 if isinstance(key, str): 

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

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

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

113 # self.__dict__ 

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

115 raise AttributeError( 

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

117 ) 

118 try: 

119 self.__setitem__(key, value) 

120 except KeyError as e: 

121 raise AttributeError(e) from e 

122 

123 def __getattr__(self, key: str) -> Any: 

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

125 

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

127 :exc:`AttributeError`. 

128 

129 Examples 

130 -------- 

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

132 >>> s.a 

133 10 

134 >>> type(s.get) 

135 <...method'> 

136 >>> try: 

137 ... s.b 

138 ... except AttributeError: 

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

140 ... 

141 I don't have that key 

142 """ 

143 try: 

144 result = self[key] 

145 except KeyError as e: 

146 raise AttributeError(key) from e 

147 else: 

148 return result 

149 

150 def __iadd__(self, other): 

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

152 

153 Examples 

154 -------- 

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

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

157 >>> s += s2 

158 >>> sorted(s.keys()) 

159 ['a', 'b', 'c'] 

160 """ 

161 self.merge(other) 

162 return self 

163 

164 def __add__(self,other): 

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

166 

167 Examples 

168 -------- 

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

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

171 >>> s = s1 + s2 

172 >>> sorted(s.keys()) 

173 ['a', 'b', 'c'] 

174 """ 

175 sout = self.copy() 

176 sout.merge(other) 

177 return sout 

178 

179 def __sub__(self,other): 

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

181 

182 Examples 

183 -------- 

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

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

186 >>> s = s1 - s2 

187 >>> s 

188 {'b': 30} 

189 """ 

190 sout = self.copy() 

191 sout -= other 

192 return sout 

193 

194 def __isub__(self,other): 

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

196 

197 Examples 

198 -------- 

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

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

201 >>> s1 -= s2 

202 >>> s1 

203 {'b': 30} 

204 """ 

205 for k in other.keys(): 

206 if k in self: 

207 del self[k] 

208 return self 

209 

210 def __dict_invert(self, data): 

211 """Helper function for merge. 

212 

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

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

215 """ 

216 outdict = {} 

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

218 if isinstance(lst, str): 

219 lst = lst.split() 

220 for entry in lst: 

221 outdict[entry] = k 

222 return outdict 

223 

224 def dict(self): 

225 return self 

226 

227 def copy(self): 

228 """Return a copy as a Struct. 

229 

230 Examples 

231 -------- 

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

233 >>> s2 = s.copy() 

234 >>> type(s2) is Struct 

235 True 

236 """ 

237 return Struct(dict.copy(self)) 

238 

239 def hasattr(self, key): 

240 """hasattr function available as a method. 

241 

242 Implemented like has_key. 

243 

244 Examples 

245 -------- 

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

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

248 True 

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

250 False 

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

252 False 

253 """ 

254 return key in self 

255 

256 def allow_new_attr(self, allow = True): 

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

258 

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

260 tries to change already exists in this Struct. 

261 """ 

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

263 

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

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

266 

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

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

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

270 what to do. 

271 

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

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

274 behavior). 

275 

276 Parameters 

277 ---------- 

278 __loc_data__ : dict, Struct 

279 The data to merge into self 

280 __conflict_solve : dict 

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

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

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

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

285 'a b c'. 

286 **kw : dict 

287 Additional key, value pairs to merge in 

288 

289 Notes 

290 ----- 

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

292 solve key conflicts. Here is an example:: 

293 

294 __conflict_solve = dict( 

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

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

297 ) 

298 

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

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

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

302 

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

304 

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

306 form:: 

307 

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

309 

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

311 

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

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

314 easiest explanation is their implementation:: 

315 

316 preserve = lambda old,new: old 

317 update = lambda old,new: new 

318 add = lambda old,new: old + new 

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

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

321 

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

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

324 the appropriate functions for you. 

325 

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

327 construct your own functions. 

328 

329 Examples 

330 -------- 

331 This show the default policy: 

332 

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

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

335 >>> s.merge(s2) 

336 >>> sorted(s.items()) 

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

338 

339 Now, show how to specify a conflict dict: 

340 

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

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

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

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

345 >>> sorted(s.items()) 

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

347 """ 

348 

349 data_dict = dict(__loc_data__,**kw) 

350 

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

352 # the value that will go in the new struct 

353 preserve = lambda old,new: old 

354 update = lambda old,new: new 

355 add = lambda old,new: old + new 

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

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

358 

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

360 conflict_solve = dict.fromkeys(self, preserve) 

361 

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

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

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

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

366 if __conflict_solve: 

367 inv_conflict_solve_user = __conflict_solve.copy() 

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

369 ('add',add), ('add_flip',add_flip), 

370 ('add_s',add_s)]: 

371 if name in inv_conflict_solve_user.keys(): 

372 inv_conflict_solve_user[func] = inv_conflict_solve_user[name] 

373 del inv_conflict_solve_user[name] 

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

375 for key in data_dict: 

376 if key not in self: 

377 self[key] = data_dict[key] 

378 else: 

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

380