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