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
« 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.
3Can probably be replaced by types.SimpleNamespace from Python 3.3
4"""
6__all__ = ["Struct"]
9class Struct(dict):
10 """A dict subclass with attribute style access.
12 This dict subclass has a a few extra features:
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 """
22 _allownew = True
24 def __init__(self, *args, **kw):
25 """Initialize with a dictionary, another Struct, or data.
27 Parameters
28 ----------
29 *args : dict, Struct
30 Initialize with one dict or Struct
31 **kw : dict
32 Initialize with key, value pairs.
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)
48 def __setitem__(self, key, value):
49 """Set an item with check for allownew.
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)
70 def __setattr__(self, key, value):
71 """Set an attr with protection of class members.
73 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
74 :exc:`AttributeError`.
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
102 def __getattr__(self, key):
103 """Get an attr by calling :meth:`dict.__getitem__`.
105 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
106 :exc:`AttributeError`.
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
129 def __iadd__(self, other):
130 """s += s2 is a shorthand for s.merge(s2).
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
143 def __add__(self, other):
144 """s + s2 -> New Struct made from s.merge(s2).
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
158 def __sub__(self, other):
159 """s1 - s2 -> remove keys in s2 from s1.
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
173 def __isub__(self, other):
174 """Inplace remove keys from self that are in other.
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
189 def __dict_invert(self, data):
190 """Helper function for merge.
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
203 def dict(self): # noqa
204 """Get the dict representation of the struct."""
205 return self
207 def copy(self):
208 """Return a copy as a Struct.
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))
219 def hasattr(self, key): # noqa
220 """hasattr function available as a method.
222 Implemented like has_key.
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
236 def allow_new_attr(self, allow=True):
237 """Set whether new attributes can be created in this Struct.
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)
244 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
245 """Merge two Structs with customizable conflict resolution.
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.
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).
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
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::
274 __conflict_solve = dict(
275 func1=['a','b','c'],
276 func2=['d','e']
277 )
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::
283 __conflict_solve = dict(func1='a b c',func2='d e')
285 These functions will be called for each key they apply to with the
286 form::
288 func1(self['a'], other['a'])
290 The return value is used as the final merged value.
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::
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!
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.
306 For more complicated conflict resolution policies, you still need to
307 construct your own functions.
309 Examples
310 --------
311 This show the default policy:
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)]
319 Now, show how to specify a conflict dict:
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 """
329 data_dict = dict(__loc_data__, **kw)
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
339 # default policy is to keep current keys when there's a conflict
340 conflict_solve = dict.fromkeys(self, preserve)
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])