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