##// END OF EJS Templates
Fix doctests that relied on dict ordering in IPython.utils
Thomas Kluyver -
Show More
@@ -1,395 +1,393 b''
1 1 # encoding: utf-8
2 2 """A dict subclass that supports attribute style access.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez (original)
7 7 * Brian Granger (refactoring to a dict subclass)
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 from IPython.utils.data import list2dict2
22 22
23 23 __all__ = ['Struct']
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Code
27 27 #-----------------------------------------------------------------------------
28 28
29 29
30 30 class Struct(dict):
31 31 """A dict subclass with attribute style access.
32 32
33 33 This dict subclass has a a few extra features:
34 34
35 35 * Attribute style access.
36 36 * Protection of class members (like keys, items) when using attribute
37 37 style access.
38 38 * The ability to restrict assignment to only existing keys.
39 39 * Intelligent merging.
40 40 * Overloaded operators.
41 41 """
42 42 _allownew = True
43 43 def __init__(self, *args, **kw):
44 44 """Initialize with a dictionary, another Struct, or data.
45 45
46 46 Parameters
47 47 ----------
48 48 args : dict, Struct
49 49 Initialize with one dict or Struct
50 50 kw : dict
51 51 Initialize with key, value pairs.
52 52
53 53 Examples
54 54 --------
55 55
56 56 >>> s = Struct(a=10,b=30)
57 57 >>> s.a
58 58 10
59 59 >>> s.b
60 60 30
61 61 >>> s2 = Struct(s,c=30)
62 >>> s2.keys()
63 ['a', 'c', 'b']
62 >>> sorted(s2.keys())
63 ['a', 'b', 'c']
64 64 """
65 65 object.__setattr__(self, '_allownew', True)
66 66 dict.__init__(self, *args, **kw)
67 67
68 68 def __setitem__(self, key, value):
69 69 """Set an item with check for allownew.
70 70
71 71 Examples
72 72 --------
73 73
74 74 >>> s = Struct()
75 75 >>> s['a'] = 10
76 76 >>> s.allow_new_attr(False)
77 77 >>> s['a'] = 10
78 78 >>> s['a']
79 79 10
80 80 >>> try:
81 81 ... s['b'] = 20
82 82 ... except KeyError:
83 83 ... print 'this is not allowed'
84 84 ...
85 85 this is not allowed
86 86 """
87 87 if not self._allownew and not self.has_key(key):
88 88 raise KeyError(
89 89 "can't create new attribute %s when allow_new_attr(False)" % key)
90 90 dict.__setitem__(self, key, value)
91 91
92 92 def __setattr__(self, key, value):
93 93 """Set an attr with protection of class members.
94 94
95 95 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
96 96 :exc:`AttributeError`.
97 97
98 98 Examples
99 99 --------
100 100
101 101 >>> s = Struct()
102 102 >>> s.a = 10
103 103 >>> s.a
104 104 10
105 105 >>> try:
106 106 ... s.get = 10
107 107 ... except AttributeError:
108 108 ... print "you can't set a class member"
109 109 ...
110 110 you can't set a class member
111 111 """
112 112 # If key is an str it might be a class member or instance var
113 113 if isinstance(key, str):
114 114 # I can't simply call hasattr here because it calls getattr, which
115 115 # calls self.__getattr__, which returns True for keys in
116 116 # self._data. But I only want keys in the class and in
117 117 # self.__dict__
118 118 if key in self.__dict__ or hasattr(Struct, key):
119 119 raise AttributeError(
120 120 'attr %s is a protected member of class Struct.' % key
121 121 )
122 122 try:
123 123 self.__setitem__(key, value)
124 124 except KeyError, e:
125 125 raise AttributeError(e)
126 126
127 127 def __getattr__(self, key):
128 128 """Get an attr by calling :meth:`dict.__getitem__`.
129 129
130 130 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
131 131 :exc:`AttributeError`.
132 132
133 133 Examples
134 134 --------
135 135
136 136 >>> s = Struct(a=10)
137 137 >>> s.a
138 138 10
139 139 >>> type(s.get)
140 140 <... 'builtin_function_or_method'>
141 141 >>> try:
142 142 ... s.b
143 143 ... except AttributeError:
144 144 ... print "I don't have that key"
145 145 ...
146 146 I don't have that key
147 147 """
148 148 try:
149 149 result = self[key]
150 150 except KeyError:
151 151 raise AttributeError(key)
152 152 else:
153 153 return result
154 154
155 155 def __iadd__(self, other):
156 156 """s += s2 is a shorthand for s.merge(s2).
157 157
158 158 Examples
159 159 --------
160 160
161 161 >>> s = Struct(a=10,b=30)
162 162 >>> s2 = Struct(a=20,c=40)
163 163 >>> s += s2
164 >>> s
165 {'a': 10, 'c': 40, 'b': 30}
164 >>> sorted(s.keys())
165 ['a', 'b', 'c']
166 166 """
167 167 self.merge(other)
168 168 return self
169 169
170 170 def __add__(self,other):
171 171 """s + s2 -> New Struct made from s.merge(s2).
172 172
173 173 Examples
174 174 --------
175 175
176 176 >>> s1 = Struct(a=10,b=30)
177 177 >>> s2 = Struct(a=20,c=40)
178 178 >>> s = s1 + s2
179 >>> s
180 {'a': 10, 'c': 40, 'b': 30}
179 >>> sorted(s.keys())
180 ['a', 'b', 'c']
181 181 """
182 182 sout = self.copy()
183 183 sout.merge(other)
184 184 return sout
185 185
186 186 def __sub__(self,other):
187 187 """s1 - s2 -> remove keys in s2 from s1.
188 188
189 189 Examples
190 190 --------
191 191
192 192 >>> s1 = Struct(a=10,b=30)
193 193 >>> s2 = Struct(a=40)
194 194 >>> s = s1 - s2
195 195 >>> s
196 196 {'b': 30}
197 197 """
198 198 sout = self.copy()
199 199 sout -= other
200 200 return sout
201 201
202 202 def __isub__(self,other):
203 203 """Inplace remove keys from self that are in other.
204 204
205 205 Examples
206 206 --------
207 207
208 208 >>> s1 = Struct(a=10,b=30)
209 209 >>> s2 = Struct(a=40)
210 210 >>> s1 -= s2
211 211 >>> s1
212 212 {'b': 30}
213 213 """
214 214 for k in other.keys():
215 215 if self.has_key(k):
216 216 del self[k]
217 217 return self
218 218
219 219 def __dict_invert(self, data):
220 220 """Helper function for merge.
221 221
222 222 Takes a dictionary whose values are lists and returns a dict with
223 223 the elements of each list as keys and the original keys as values.
224 224 """
225 225 outdict = {}
226 226 for k,lst in data.items():
227 227 if isinstance(lst, str):
228 228 lst = lst.split()
229 229 for entry in lst:
230 230 outdict[entry] = k
231 231 return outdict
232 232
233 233 def dict(self):
234 234 return self
235 235
236 236 def copy(self):
237 237 """Return a copy as a Struct.
238 238
239 239 Examples
240 240 --------
241 241
242 242 >>> s = Struct(a=10,b=30)
243 243 >>> s2 = s.copy()
244 >>> s2
245 {'a': 10, 'b': 30}
246 >>> type(s2).__name__
247 'Struct'
244 >>> type(s2) is Struct
245 True
248 246 """
249 247 return Struct(dict.copy(self))
250 248
251 249 def hasattr(self, key):
252 250 """hasattr function available as a method.
253 251
254 252 Implemented like has_key.
255 253
256 254 Examples
257 255 --------
258 256
259 257 >>> s = Struct(a=10)
260 258 >>> s.hasattr('a')
261 259 True
262 260 >>> s.hasattr('b')
263 261 False
264 262 >>> s.hasattr('get')
265 263 False
266 264 """
267 265 return self.has_key(key)
268 266
269 267 def allow_new_attr(self, allow = True):
270 268 """Set whether new attributes can be created in this Struct.
271 269
272 270 This can be used to catch typos by verifying that the attribute user
273 271 tries to change already exists in this Struct.
274 272 """
275 273 object.__setattr__(self, '_allownew', allow)
276 274
277 275 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
278 276 """Merge two Structs with customizable conflict resolution.
279 277
280 278 This is similar to :meth:`update`, but much more flexible. First, a
281 279 dict is made from data+key=value pairs. When merging this dict with
282 280 the Struct S, the optional dictionary 'conflict' is used to decide
283 281 what to do.
284 282
285 283 If conflict is not given, the default behavior is to preserve any keys
286 284 with their current value (the opposite of the :meth:`update` method's
287 285 behavior).
288 286
289 287 Parameters
290 288 ----------
291 289 __loc_data : dict, Struct
292 290 The data to merge into self
293 291 __conflict_solve : dict
294 292 The conflict policy dict. The keys are binary functions used to
295 293 resolve the conflict and the values are lists of strings naming
296 294 the keys the conflict resolution function applies to. Instead of
297 295 a list of strings a space separated string can be used, like
298 296 'a b c'.
299 297 kw : dict
300 298 Additional key, value pairs to merge in
301 299
302 300 Notes
303 301 -----
304 302
305 303 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
306 304 solve key conflicts. Here is an example::
307 305
308 306 __conflict_solve = dict(
309 307 func1=['a','b','c'],
310 308 func2=['d','e']
311 309 )
312 310
313 311 In this case, the function :func:`func1` will be used to resolve
314 312 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
315 313 keys 'd' and 'e'. This could also be written as::
316 314
317 315 __conflict_solve = dict(func1='a b c',func2='d e')
318 316
319 317 These functions will be called for each key they apply to with the
320 318 form::
321 319
322 320 func1(self['a'], other['a'])
323 321
324 322 The return value is used as the final merged value.
325 323
326 324 As a convenience, merge() provides five (the most commonly needed)
327 325 pre-defined policies: preserve, update, add, add_flip and add_s. The
328 326 easiest explanation is their implementation::
329 327
330 328 preserve = lambda old,new: old
331 329 update = lambda old,new: new
332 330 add = lambda old,new: old + new
333 331 add_flip = lambda old,new: new + old # note change of order!
334 332 add_s = lambda old,new: old + ' ' + new # only for str!
335 333
336 334 You can use those four words (as strings) as keys instead
337 335 of defining them as functions, and the merge method will substitute
338 336 the appropriate functions for you.
339 337
340 338 For more complicated conflict resolution policies, you still need to
341 339 construct your own functions.
342 340
343 341 Examples
344 342 --------
345 343
346 344 This show the default policy:
347 345
348 346 >>> s = Struct(a=10,b=30)
349 347 >>> s2 = Struct(a=20,c=40)
350 348 >>> s.merge(s2)
351 >>> s
352 {'a': 10, 'c': 40, 'b': 30}
349 >>> sorted(s.items())
350 [('a', 10), ('b', 30), ('c', 40)]
353 351
354 352 Now, show how to specify a conflict dict:
355 353
356 354 >>> s = Struct(a=10,b=30)
357 355 >>> s2 = Struct(a=20,b=40)
358 356 >>> conflict = {'update':'a','add':'b'}
359 357 >>> s.merge(s2,conflict)
360 >>> s
361 {'a': 20, 'b': 70}
358 >>> sorted(s.items())
359 [('a', 20), ('b', 70)]
362 360 """
363 361
364 362 data_dict = dict(__loc_data__,**kw)
365 363
366 364 # policies for conflict resolution: two argument functions which return
367 365 # the value that will go in the new struct
368 366 preserve = lambda old,new: old
369 367 update = lambda old,new: new
370 368 add = lambda old,new: old + new
371 369 add_flip = lambda old,new: new + old # note change of order!
372 370 add_s = lambda old,new: old + ' ' + new
373 371
374 372 # default policy is to keep current keys when there's a conflict
375 373 conflict_solve = list2dict2(self.keys(), default = preserve)
376 374
377 375 # the conflict_solve dictionary is given by the user 'inverted': we
378 376 # need a name-function mapping, it comes as a function -> names
379 377 # dict. Make a local copy (b/c we'll make changes), replace user
380 378 # strings for the three builtin policies and invert it.
381 379 if __conflict_solve:
382 380 inv_conflict_solve_user = __conflict_solve.copy()
383 381 for name, func in [('preserve',preserve), ('update',update),
384 382 ('add',add), ('add_flip',add_flip),
385 383 ('add_s',add_s)]:
386 384 if name in inv_conflict_solve_user.keys():
387 385 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
388 386 del inv_conflict_solve_user[name]
389 387 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
390 388 for key in data_dict:
391 389 if key not in self:
392 390 self[key] = data_dict[key]
393 391 else:
394 392 self[key] = conflict_solve[key](self[key],data_dict[key])
395 393
@@ -1,166 +1,166 b''
1 1 """Utilities to manipulate JSON objects.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import re
15 15 import sys
16 16 import types
17 17 from datetime import datetime
18 18
19 19 from IPython.utils import py3compat
20 20 from IPython.utils.encoding import DEFAULT_ENCODING
21 21 from IPython.utils import text
22 22 next_attr_name = '__next__' if py3compat.PY3 else 'next'
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Globals and constants
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # timestamp formats
29 29 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
30 30 ISO8601_PAT=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Classes and functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 def rekey(dikt):
37 37 """Rekey a dict that has been forced to use str keys where there should be
38 38 ints by json."""
39 39 for k in dikt.iterkeys():
40 40 if isinstance(k, basestring):
41 41 ik=fk=None
42 42 try:
43 43 ik = int(k)
44 44 except ValueError:
45 45 try:
46 46 fk = float(k)
47 47 except ValueError:
48 48 continue
49 49 if ik is not None:
50 50 nk = ik
51 51 else:
52 52 nk = fk
53 53 if nk in dikt:
54 54 raise KeyError("already have key %r"%nk)
55 55 dikt[nk] = dikt.pop(k)
56 56 return dikt
57 57
58 58
59 59 def extract_dates(obj):
60 60 """extract ISO8601 dates from unpacked JSON"""
61 61 if isinstance(obj, dict):
62 62 obj = dict(obj) # don't clobber
63 63 for k,v in obj.iteritems():
64 64 obj[k] = extract_dates(v)
65 65 elif isinstance(obj, (list, tuple)):
66 66 obj = [ extract_dates(o) for o in obj ]
67 67 elif isinstance(obj, basestring):
68 68 if ISO8601_PAT.match(obj):
69 69 obj = datetime.strptime(obj, ISO8601)
70 70 return obj
71 71
72 72 def squash_dates(obj):
73 73 """squash datetime objects into ISO8601 strings"""
74 74 if isinstance(obj, dict):
75 75 obj = dict(obj) # don't clobber
76 76 for k,v in obj.iteritems():
77 77 obj[k] = squash_dates(v)
78 78 elif isinstance(obj, (list, tuple)):
79 79 obj = [ squash_dates(o) for o in obj ]
80 80 elif isinstance(obj, datetime):
81 81 obj = obj.strftime(ISO8601)
82 82 return obj
83 83
84 84 def date_default(obj):
85 85 """default function for packing datetime objects in JSON."""
86 86 if isinstance(obj, datetime):
87 87 return obj.strftime(ISO8601)
88 88 else:
89 89 raise TypeError("%r is not JSON serializable"%obj)
90 90
91 91
92 92
93 93 def json_clean(obj):
94 94 """Clean an object to ensure it's safe to encode in JSON.
95 95
96 96 Atomic, immutable objects are returned unmodified. Sets and tuples are
97 97 converted to lists, lists are copied and dicts are also copied.
98 98
99 99 Note: dicts whose keys could cause collisions upon encoding (such as a dict
100 100 with both the number 1 and the string '1' as keys) will cause a ValueError
101 101 to be raised.
102 102
103 103 Parameters
104 104 ----------
105 105 obj : any python object
106 106
107 107 Returns
108 108 -------
109 109 out : object
110 110
111 111 A version of the input which will not cause an encoding error when
112 112 encoded as JSON. Note that this function does not *encode* its inputs,
113 113 it simply sanitizes it so that there will be no encoding errors later.
114 114
115 115 Examples
116 116 --------
117 117 >>> json_clean(4)
118 118 4
119 119 >>> json_clean(range(10))
120 120 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
121 >>> json_clean(dict(x=1, y=2))
122 {'y': 2, 'x': 1}
123 >>> json_clean(dict(x=1, y=2, z=[1,2,3]))
124 {'y': 2, 'x': 1, 'z': [1, 2, 3]}
121 >>> sorted(json_clean(dict(x=1, y=2)).items())
122 [('x', 1), ('y', 2)]
123 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
124 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
125 125 >>> json_clean(True)
126 126 True
127 127 """
128 128 # types that are 'atomic' and ok in json as-is. bool doesn't need to be
129 129 # listed explicitly because bools pass as int instances
130 130 atomic_ok = (unicode, int, float, types.NoneType)
131 131
132 132 # containers that we need to convert into lists
133 133 container_to_list = (tuple, set, types.GeneratorType)
134 134
135 135 if isinstance(obj, atomic_ok):
136 136 return obj
137 137
138 138 if isinstance(obj, bytes):
139 139 return obj.decode(DEFAULT_ENCODING, 'replace')
140 140
141 141 if isinstance(obj, container_to_list) or (
142 142 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
143 143 obj = list(obj)
144 144
145 145 if isinstance(obj, list):
146 146 return [json_clean(x) for x in obj]
147 147
148 148 if isinstance(obj, dict):
149 149 # First, validate that the dict won't lose data in conversion due to
150 150 # key collisions after stringification. This can happen with keys like
151 151 # True and 'true' or 1 and '1', which collide in JSON.
152 152 nkeys = len(obj)
153 153 nkeys_collapsed = len(set(map(str, obj)))
154 154 if nkeys != nkeys_collapsed:
155 155 raise ValueError('dict can not be safely converted to JSON: '
156 156 'key collision would lead to dropped values')
157 157 # If all OK, proceed by making the new dict that will be json-safe
158 158 out = {}
159 159 for k,v in obj.iteritems():
160 160 out[str(k)] = json_clean(v)
161 161 return out
162 162
163 163 # If we get here, we don't know how to handle the object, so we just get
164 164 # its repr and return that. This will catch lambdas, open sockets, class
165 165 # objects, and any other complicated contraption that json can't encode
166 166 return repr(obj)
General Comments 0
You need to be logged in to leave comments. Login now