##// END OF EJS Templates
P3K: fix DeprecationWarning under Python 3.x (base64.encodestring is deprecated)
Mikhail Korobov -
Show More
@@ -1,207 +1,213 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 math
15 15 import re
16 16 import sys
17 17 import types
18 from base64 import encodestring
19 18 from datetime import datetime
20 19
20 try:
21 # base64.encodestring is deprecated in Python 3.x
22 from base64 import encodebytes
23 except ImportError:
24 # Python 2.x
25 from base64 import encodestring as encodebytes
26
21 27 from IPython.utils import py3compat
22 28 from IPython.utils.encoding import DEFAULT_ENCODING
23 29 from IPython.utils import text
24 30 next_attr_name = '__next__' if py3compat.PY3 else 'next'
25 31
26 32 #-----------------------------------------------------------------------------
27 33 # Globals and constants
28 34 #-----------------------------------------------------------------------------
29 35
30 36 # timestamp formats
31 37 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
32 38 ISO8601_PAT=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
33 39
34 40 #-----------------------------------------------------------------------------
35 41 # Classes and functions
36 42 #-----------------------------------------------------------------------------
37 43
38 44 def rekey(dikt):
39 45 """Rekey a dict that has been forced to use str keys where there should be
40 46 ints by json."""
41 47 for k in dikt.iterkeys():
42 48 if isinstance(k, basestring):
43 49 ik=fk=None
44 50 try:
45 51 ik = int(k)
46 52 except ValueError:
47 53 try:
48 54 fk = float(k)
49 55 except ValueError:
50 56 continue
51 57 if ik is not None:
52 58 nk = ik
53 59 else:
54 60 nk = fk
55 61 if nk in dikt:
56 62 raise KeyError("already have key %r"%nk)
57 63 dikt[nk] = dikt.pop(k)
58 64 return dikt
59 65
60 66
61 67 def extract_dates(obj):
62 68 """extract ISO8601 dates from unpacked JSON"""
63 69 if isinstance(obj, dict):
64 70 obj = dict(obj) # don't clobber
65 71 for k,v in obj.iteritems():
66 72 obj[k] = extract_dates(v)
67 73 elif isinstance(obj, (list, tuple)):
68 74 obj = [ extract_dates(o) for o in obj ]
69 75 elif isinstance(obj, basestring):
70 76 if ISO8601_PAT.match(obj):
71 77 obj = datetime.strptime(obj, ISO8601)
72 78 return obj
73 79
74 80 def squash_dates(obj):
75 81 """squash datetime objects into ISO8601 strings"""
76 82 if isinstance(obj, dict):
77 83 obj = dict(obj) # don't clobber
78 84 for k,v in obj.iteritems():
79 85 obj[k] = squash_dates(v)
80 86 elif isinstance(obj, (list, tuple)):
81 87 obj = [ squash_dates(o) for o in obj ]
82 88 elif isinstance(obj, datetime):
83 89 obj = obj.strftime(ISO8601)
84 90 return obj
85
91
86 92 def date_default(obj):
87 93 """default function for packing datetime objects in JSON."""
88 94 if isinstance(obj, datetime):
89 95 return obj.strftime(ISO8601)
90 96 else:
91 97 raise TypeError("%r is not JSON serializable"%obj)
92 98
93 99
94 100 # constants for identifying png/jpeg data
95 101 PNG = b'\x89PNG\r\n\x1a\n'
96 102 JPEG = b'\xff\xd8'
97 103
98 104 def encode_images(format_dict):
99 105 """b64-encodes images in a displaypub format dict
100
106
101 107 Perhaps this should be handled in json_clean itself?
102
108
103 109 Parameters
104 110 ----------
105
111
106 112 format_dict : dict
107 113 A dictionary of display data keyed by mime-type
108
114
109 115 Returns
110 116 -------
111
117
112 118 format_dict : dict
113 119 A copy of the same dictionary,
114 120 but binary image data ('image/png' or 'image/jpeg')
115 121 is base64-encoded.
116
122
117 123 """
118 124 encoded = format_dict.copy()
119 125 pngdata = format_dict.get('image/png')
120 126 if isinstance(pngdata, bytes) and pngdata[:8] == PNG:
121 encoded['image/png'] = encodestring(pngdata).decode('ascii')
127 encoded['image/png'] = encodebytes(pngdata).decode('ascii')
122 128 jpegdata = format_dict.get('image/jpeg')
123 129 if isinstance(jpegdata, bytes) and jpegdata[:2] == JPEG:
124 encoded['image/jpeg'] = encodestring(jpegdata).decode('ascii')
130 encoded['image/jpeg'] = encodebytes(jpegdata).decode('ascii')
125 131 return encoded
126 132
127 133
128 134 def json_clean(obj):
129 135 """Clean an object to ensure it's safe to encode in JSON.
130
136
131 137 Atomic, immutable objects are returned unmodified. Sets and tuples are
132 138 converted to lists, lists are copied and dicts are also copied.
133 139
134 140 Note: dicts whose keys could cause collisions upon encoding (such as a dict
135 141 with both the number 1 and the string '1' as keys) will cause a ValueError
136 142 to be raised.
137 143
138 144 Parameters
139 145 ----------
140 146 obj : any python object
141 147
142 148 Returns
143 149 -------
144 150 out : object
145
151
146 152 A version of the input which will not cause an encoding error when
147 153 encoded as JSON. Note that this function does not *encode* its inputs,
148 154 it simply sanitizes it so that there will be no encoding errors later.
149 155
150 156 Examples
151 157 --------
152 158 >>> json_clean(4)
153 159 4
154 160 >>> json_clean(range(10))
155 161 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
156 162 >>> sorted(json_clean(dict(x=1, y=2)).items())
157 163 [('x', 1), ('y', 2)]
158 164 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
159 165 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
160 166 >>> json_clean(True)
161 167 True
162 168 """
163 169 # types that are 'atomic' and ok in json as-is. bool doesn't need to be
164 170 # listed explicitly because bools pass as int instances
165 171 atomic_ok = (unicode, int, types.NoneType)
166
172
167 173 # containers that we need to convert into lists
168 174 container_to_list = (tuple, set, types.GeneratorType)
169
175
170 176 if isinstance(obj, float):
171 177 # cast out-of-range floats to their reprs
172 178 if math.isnan(obj) or math.isinf(obj):
173 179 return repr(obj)
174 180 return obj
175
181
176 182 if isinstance(obj, atomic_ok):
177 183 return obj
178
184
179 185 if isinstance(obj, bytes):
180 186 return obj.decode(DEFAULT_ENCODING, 'replace')
181
187
182 188 if isinstance(obj, container_to_list) or (
183 189 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
184 190 obj = list(obj)
185
191
186 192 if isinstance(obj, list):
187 193 return [json_clean(x) for x in obj]
188 194
189 195 if isinstance(obj, dict):
190 196 # First, validate that the dict won't lose data in conversion due to
191 197 # key collisions after stringification. This can happen with keys like
192 198 # True and 'true' or 1 and '1', which collide in JSON.
193 199 nkeys = len(obj)
194 200 nkeys_collapsed = len(set(map(str, obj)))
195 201 if nkeys != nkeys_collapsed:
196 202 raise ValueError('dict can not be safely converted to JSON: '
197 203 'key collision would lead to dropped values')
198 204 # If all OK, proceed by making the new dict that will be json-safe
199 205 out = {}
200 206 for k,v in obj.iteritems():
201 207 out[str(k)] = json_clean(v)
202 208 return out
203 209
204 210 # If we get here, we don't know how to handle the object, so we just get
205 211 # its repr and return that. This will catch lambdas, open sockets, class
206 212 # objects, and any other complicated contraption that json can't encode
207 213 return repr(obj)
General Comments 0
You need to be logged in to leave comments. Login now