Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/nbformat/_struct.py: 23%

75 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

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

2 

3Can probably be replaced by types.SimpleNamespace from Python 3.3 

4""" 

5 

6__all__ = ["Struct"] 

7 

8 

9class Struct(dict): 

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

11 

12 This dict subclass has a a few extra features: 

13 

14 * Attribute style access. 

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

16 style access. 

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

18 * Intelligent merging. 

19 * Overloaded operators. 

20 """ 

21 

22 _allownew = True 

23 

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

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

26 

27 Parameters 

28 ---------- 

29 *args : dict, Struct 

30 Initialize with one dict or Struct 

31 **kw : dict 

32 Initialize with key, value pairs. 

33 

34 Examples 

35 -------- 

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

37 >>> s.a 

38 10 

39 >>> s.b 

40 30 

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

42 >>> sorted(s2.keys()) 

43 ['a', 'b', 'c'] 

44 """ 

45 object.__setattr__(self, "_allownew", True) 

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

47 

48 def __setitem__(self, key, value): 

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

50 

51 Examples 

52 -------- 

53 >>> s = Struct() 

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

55 >>> s.allow_new_attr(False) 

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

57 >>> s['a'] 

58 10 

59 >>> try: 

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

61 ... except KeyError: 

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

63 ... 

64 this is not allowed 

65 """ 

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

67 raise KeyError("can't create new attribute %s when allow_new_attr(False)" % key) 

68 dict.__setitem__(self, key, value) 

69 

70 def __setattr__(self, key, value): 

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

72 

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

74 :exc:`AttributeError`. 

75 

76 Examples 

77 -------- 

78 >>> s = Struct() 

79 >>> s.a = 10 

80 >>> s.a 

81 10 

82 >>> try: 

83 ... s.get = 10 

84 ... except AttributeError: 

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

86 ... 

87 you can't set a class member 

88 """ 

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

90 if isinstance(key, str): # noqa 

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

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

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

94 # self.__dict__ 

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

96 raise AttributeError("attr %s is a protected member of class Struct." % key) 

97 try: 

98 self.__setitem__(key, value) 

99 except KeyError as e: 

100 raise AttributeError(e) from None 

101 

102 def __getattr__(self, key): 

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

104 

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

106 :exc:`AttributeError`. 

107 

108 Examples 

109 -------- 

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

111 >>> s.a 

112 10 

113 >>> type(s.get) 

114 <... 'builtin_function_or_method'> 

115 >>> try: 

116 ... s.b 

117 ... except AttributeError: 

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

119 ... 

120 I don't have that key 

121 """ 

122 try: 

123 result = self[key] 

124 except KeyError: 

125 raise AttributeError(key) from None 

126 else: 

127 return result 

128 

129 def __iadd__(self, other): 

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

131 

132 Examples 

133 -------- 

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

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

136 >>> s += s2 

137 >>> sorted(s.keys()) 

138 ['a', 'b', 'c'] 

139 """ 

140 self.merge(other) 

141 return self 

142 

143 def __add__(self, other): 

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

145 

146 Examples 

147 -------- 

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

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

150 >>> s = s1 + s2 

151 >>> sorted(s.keys()) 

152 ['a', 'b', 'c'] 

153 """ 

154 sout = self.copy() 

155 sout.merge(other) 

156 return sout 

157 

158 def __sub__(self, other): 

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

160 

161 Examples 

162 -------- 

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

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

165 >>> s = s1 - s2 

166 >>> s 

167 {'b': 30} 

168 """ 

169 sout = self.copy() 

170 sout -= other 

171 return sout 

172 

173 def __isub__(self, other): 

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

175 

176 Examples 

177 -------- 

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

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

180 >>> s1 -= s2 

181 >>> s1 

182 {'b': 30} 

183 """ 

184 for k in other: 

185 if k in self: 

186 del self[k] 

187 return self 

188 

189 def __dict_invert(self, data): 

190 """Helper function for merge. 

191 

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

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

194 """ 

195 outdict = {} 

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

197 if isinstance(lst, str): 

198 lst = lst.split() # noqa 

199 for entry in lst: 

200 outdict[entry] = k 

201 return outdict 

202 

203 def dict(self): # noqa 

204 """Get the dict representation of the struct.""" 

205 return self 

206 

207 def copy(self): 

208 """Return a copy as a Struct. 

209 

210 Examples 

211 -------- 

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

213 >>> s2 = s.copy() 

214 >>> type(s2) is Struct 

215 True 

216 """ 

217 return Struct(dict.copy(self)) 

218 

219 def hasattr(self, key): # noqa 

220 """hasattr function available as a method. 

221 

222 Implemented like has_key. 

223 

224 Examples 

225 -------- 

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

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

228 True 

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

230 False 

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

232 False 

233 """ 

234 return key in self 

235 

236 def allow_new_attr(self, allow=True): 

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

238 

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

240 tries to change already exists in this Struct. 

241 """ 

242 object.__setattr__(self, "_allownew", allow) 

243 

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

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

246 

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

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

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

250 what to do. 

251 

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

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

254 behavior). 

255 

256 Parameters 

257 ---------- 

258 __loc_data__ : dict, Struct 

259 The data to merge into self 

260 __conflict_solve : dict 

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

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

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

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

265 'a b c'. 

266 **kw : dict 

267 Additional key, value pairs to merge in 

268 

269 Notes 

270 ----- 

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

272 solve key conflicts. Here is an example:: 

273 

274 __conflict_solve = dict( 

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

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

277 ) 

278 

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

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

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

282 

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

284 

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

286 form:: 

287 

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

289 

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

291 

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

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

294 easiest explanation is their implementation:: 

295 

296 preserve = lambda old,new: old 

297 update = lambda old,new: new 

298 add = lambda old,new: old + new 

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

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

301 

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

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

304 the appropriate functions for you. 

305 

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

307 construct your own functions. 

308 

309 Examples 

310 -------- 

311 This show the default policy: 

312 

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

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

315 >>> s.merge(s2) 

316 >>> sorted(s.items()) 

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

318 

319 Now, show how to specify a conflict dict: 

320 

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

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

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

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

325 >>> sorted(s.items()) 

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

327 """ 

328 

329 data_dict = dict(__loc_data__, **kw) 

330 

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

332 # the value that will go in the new struct 

333 preserve = lambda old, new: old # noqa 

334 update = lambda old, new: new # noqa 

335 add = lambda old, new: old + new # noqa 

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

337 add_s = lambda old, new: old + " " + new # noqa 

338 

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

340 conflict_solve = dict.fromkeys(self, preserve) 

341 

342 # the confli_allownewct_solve dictionary is given by the user 'inverted': we 

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

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

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

346 if __conflict_solve: 

347 inv_conflict_solve_user = __conflict_solve.copy() 

348 for name, func in [ 

349 ("preserve", preserve), 

350 ("update", update), 

351 ("add", add), 

352 ("add_flip", add_flip), 

353 ("add_s", add_s), 

354 ]: 

355 if name in inv_conflict_solve_user: 

356 inv_conflict_solve_user[func] = inv_conflict_solve_user[name] 

357 del inv_conflict_solve_user[name] 

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

359 for key in data_dict: 

360 if key not in self: 

361 self[key] = data_dict[key] 

362 else: 

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