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
« 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.
4Authors:
6* Fernando Perez (original)
7* Brian Granger (refactoring to a dict subclass)
8"""
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#-----------------------------------------------------------------------------
17#-----------------------------------------------------------------------------
18# Imports
19#-----------------------------------------------------------------------------
21__all__ = ['Struct']
23#-----------------------------------------------------------------------------
24# Code
25#-----------------------------------------------------------------------------
28class Struct(dict):
29 """A dict subclass with attribute style access.
31 This dict subclass has a a few extra features:
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.
44 Parameters
45 ----------
46 *args : dict, Struct
47 Initialize with one dict or Struct
48 **kw : dict
49 Initialize with key, value pairs.
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)
65 def __setitem__(self, key, value):
66 """Set an item with check for allownew.
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)
88 def __setattr__(self, key, value):
89 """Set an attr with protection of class members.
91 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
92 :exc:`AttributeError`.
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
122 def __getattr__(self, key):
123 """Get an attr by calling :meth:`dict.__getitem__`.
125 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
126 :exc:`AttributeError`.
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
149 def __iadd__(self, other):
150 """s += s2 is a shorthand for s.merge(s2).
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
163 def __add__(self,other):
164 """s + s2 -> New Struct made from s.merge(s2).
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
178 def __sub__(self,other):
179 """s1 - s2 -> remove keys in s2 from s1.
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
193 def __isub__(self,other):
194 """Inplace remove keys from self that are in other.
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
209 def __dict_invert(self, data):
210 """Helper function for merge.
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
223 def dict(self):
224 return self
226 def copy(self):
227 """Return a copy as a Struct.
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))
238 def hasattr(self, key):
239 """hasattr function available as a method.
241 Implemented like has_key.
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
255 def allow_new_attr(self, allow = True):
256 """Set whether new attributes can be created in this Struct.
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)
263 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
264 """Merge two Structs with customizable conflict resolution.
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.
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).
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
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::
293 __conflict_solve = dict(
294 func1=['a','b','c'],
295 func2=['d','e']
296 )
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::
302 __conflict_solve = dict(func1='a b c',func2='d e')
304 These functions will be called for each key they apply to with the
305 form::
307 func1(self['a'], other['a'])
309 The return value is used as the final merged value.
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::
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!
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.
325 For more complicated conflict resolution policies, you still need to
326 construct your own functions.
328 Examples
329 --------
330 This show the default policy:
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)]
338 Now, show how to specify a conflict dict:
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 """
348 data_dict = dict(__loc_data__,**kw)
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
358 # default policy is to keep current keys when there's a conflict
359 conflict_solve = dict.fromkeys(self, preserve)
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])