Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(752)

Side by Side Diff: gslib/third_party/protorpc/protojson.py

Issue 698893003: Update checked in version of gsutil to version 4.6 (Closed) Base URL: http://dart.googlecode.com/svn/third_party/gsutil/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gslib/third_party/protorpc/messages.py ('k') | gslib/third_party/protorpc/util.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2010 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 """JSON support for message types.
19
20 Public classes:
21 MessageJSONEncoder: JSON encoder for message objects.
22
23 Public functions:
24 encode_message: Encodes a message in to a JSON string.
25 decode_message: Merge from a JSON string in to a message.
26 """
27
28 __author__ = 'rafek@google.com (Rafe Kaplan)'
29
30 import cStringIO
31 import base64
32 import logging
33
34 from gslib.third_party.protorpc import message_types
35 from gslib.third_party.protorpc import messages
36 from gslib.third_party.protorpc import util
37
38 __all__ = [
39 'ALTERNATIVE_CONTENT_TYPES',
40 'CONTENT_TYPE',
41 'MessageJSONEncoder',
42 'encode_message',
43 'decode_message',
44 'ProtoJson',
45 ]
46
47
48 def _load_json_module():
49 """Try to load a valid json module.
50
51 There are more than one json modules that might be installed. They are
52 mostly compatible with one another but some versions may be different.
53 This function attempts to load various json modules in a preferred order.
54 It does a basic check to guess if a loaded version of json is compatible.
55
56 Returns:
57 Compatible json module.
58
59 Raises:
60 ImportError if there are no json modules or the loaded json module is
61 not compatible with ProtoRPC.
62 """
63 first_import_error = None
64 for module_name in ['json',
65 'simplejson']:
66 try:
67 module = __import__(module_name, {}, {}, 'json')
68 if not hasattr(module, 'JSONEncoder'):
69 message = ('json library "%s" is not compatible with ProtoRPC' %
70 module_name)
71 logging.warning(message)
72 raise ImportError(message)
73 else:
74 return module
75 except ImportError, err:
76 if not first_import_error:
77 first_import_error = err
78
79 logging.error('Must use valid json library (Python 2.6 json or simplejson)')
80 raise first_import_error
81 json = _load_json_module()
82
83
84 # TODO: Rename this to MessageJsonEncoder.
85 class MessageJSONEncoder(json.JSONEncoder):
86 """Message JSON encoder class.
87
88 Extension of JSONEncoder that can build JSON from a message object.
89 """
90
91 def __init__(self, protojson_protocol=None, **kwargs):
92 """Constructor.
93
94 Args:
95 protojson_protocol: ProtoJson instance.
96 """
97 super(MessageJSONEncoder, self).__init__(**kwargs)
98 self.__protojson_protocol = protojson_protocol or ProtoJson.get_default()
99
100 def default(self, value):
101 """Return dictionary instance from a message object.
102
103 Args:
104 value: Value to get dictionary for. If not encodable, will
105 call superclasses default method.
106 """
107 if isinstance(value, messages.Enum):
108 return str(value)
109
110 if isinstance(value, messages.Message):
111 result = {}
112 for field in value.all_fields():
113 item = value.get_assigned_value(field.name)
114 if item not in (None, [], ()):
115 result[field.name] = self.__protojson_protocol.encode_field(
116 field, item)
117 # Handle unrecognized fields, so they're included when a message is
118 # decoded then encoded.
119 for unknown_key in value.all_unrecognized_fields():
120 unrecognized_field, _ = value.get_unrecognized_field_info(unknown_key)
121 result[unknown_key] = unrecognized_field
122 return result
123 else:
124 return super(MessageJSONEncoder, self).default(value)
125
126
127 class ProtoJson(object):
128 """ProtoRPC JSON implementation class.
129
130 Implementation of JSON based protocol used for serializing and deserializing
131 message objects. Instances of remote.ProtocolConfig constructor or used with
132 remote.Protocols.add_protocol. See the remote.py module for more details.
133 """
134
135 CONTENT_TYPE = 'application/json'
136 ALTERNATIVE_CONTENT_TYPES = [
137 'application/x-javascript',
138 'text/javascript',
139 'text/x-javascript',
140 'text/x-json',
141 'text/json',
142 ]
143
144 def encode_field(self, field, value):
145 """Encode a python field value to a JSON value.
146
147 Args:
148 field: A ProtoRPC field instance.
149 value: A python value supported by field.
150
151 Returns:
152 A JSON serializable value appropriate for field.
153 """
154 if isinstance(field, messages.BytesField):
155 if field.repeated:
156 value = [base64.b64encode(byte) for byte in value]
157 else:
158 value = base64.b64encode(value)
159 elif isinstance(field, message_types.DateTimeField):
160 # DateTimeField stores its data as a RFC 3339 compliant string.
161 if field.repeated:
162 value = [i.isoformat() for i in value]
163 else:
164 value = value.isoformat()
165 return value
166
167 def encode_message(self, message):
168 """Encode Message instance to JSON string.
169
170 Args:
171 Message instance to encode in to JSON string.
172
173 Returns:
174 String encoding of Message instance in protocol JSON format.
175
176 Raises:
177 messages.ValidationError if message is not initialized.
178 """
179 message.check_initialized()
180
181 return json.dumps(message, cls=MessageJSONEncoder, protojson_protocol=self)
182
183 def decode_message(self, message_type, encoded_message):
184 """Merge JSON structure to Message instance.
185
186 Args:
187 message_type: Message to decode data to.
188 encoded_message: JSON encoded version of message.
189
190 Returns:
191 Decoded instance of message_type.
192
193 Raises:
194 ValueError: If encoded_message is not valid JSON.
195 messages.ValidationError if merged message is not initialized.
196 """
197 if not encoded_message.strip():
198 return message_type()
199
200 dictionary = json.loads(encoded_message)
201 message = self.__decode_dictionary(message_type, dictionary)
202 message.check_initialized()
203 return message
204
205 def __find_variant(self, value):
206 """Find the messages.Variant type that describes this value.
207
208 Args:
209 value: The value whose variant type is being determined.
210
211 Returns:
212 The messages.Variant value that best describes value's type, or None if
213 it's a type we don't know how to handle.
214 """
215 if isinstance(value, bool):
216 return messages.Variant.BOOL
217 elif isinstance(value, (int, long)):
218 return messages.Variant.INT64
219 elif isinstance(value, float):
220 return messages.Variant.DOUBLE
221 elif isinstance(value, basestring):
222 return messages.Variant.STRING
223 elif isinstance(value, (list, tuple)):
224 # Find the most specific variant that covers all elements.
225 variant_priority = [None, messages.Variant.INT64, messages.Variant.DOUBLE,
226 messages.Variant.STRING]
227 chosen_priority = 0
228 for v in value:
229 variant = self.__find_variant(v)
230 try:
231 priority = variant_priority.index(variant)
232 except IndexError:
233 priority = -1
234 if priority > chosen_priority:
235 chosen_priority = priority
236 return variant_priority[chosen_priority]
237 # Unrecognized type.
238 return None
239
240 def __decode_dictionary(self, message_type, dictionary):
241 """Merge dictionary in to message.
242
243 Args:
244 message: Message to merge dictionary in to.
245 dictionary: Dictionary to extract information from. Dictionary
246 is as parsed from JSON. Nested objects will also be dictionaries.
247 """
248 message = message_type()
249 for key, value in dictionary.iteritems():
250 if value is None:
251 try:
252 message.reset(key)
253 except AttributeError:
254 pass # This is an unrecognized field, skip it.
255 continue
256
257 try:
258 field = message.field_by_name(key)
259 except KeyError:
260 # Save unknown values.
261 variant = self.__find_variant(value)
262 if variant:
263 if key.isdigit():
264 key = int(key)
265 message.set_unrecognized_field(key, value, variant)
266 else:
267 logging.warning('No variant found for unrecognized field: %s', key)
268 continue
269
270 # Normalize values in to a list.
271 if isinstance(value, list):
272 if not value:
273 continue
274 else:
275 value = [value]
276
277 valid_value = []
278 for item in value:
279 valid_value.append(self.decode_field(field, item))
280
281 if field.repeated:
282 existing_value = getattr(message, field.name)
283 setattr(message, field.name, valid_value)
284 else:
285 setattr(message, field.name, valid_value[-1])
286 return message
287
288 def decode_field(self, field, value):
289 """Decode a JSON value to a python value.
290
291 Args:
292 field: A ProtoRPC field instance.
293 value: A serialized JSON value.
294
295 Return:
296 A Python value compatible with field.
297 """
298 if isinstance(field, messages.EnumField):
299 try:
300 return field.type(value)
301 except TypeError:
302 raise messages.DecodeError('Invalid enum value "%s"' % value[0])
303
304 elif isinstance(field, messages.BytesField):
305 try:
306 return base64.b64decode(value)
307 except TypeError, err:
308 raise messages.DecodeError('Base64 decoding error: %s' % err)
309
310 elif isinstance(field, message_types.DateTimeField):
311 try:
312 return util.decode_datetime(value)
313 except ValueError, err:
314 raise messages.DecodeError(err)
315
316 elif (isinstance(field, messages.MessageField) and
317 issubclass(field.type, messages.Message)):
318 return self.__decode_dictionary(field.type, value)
319
320 elif (isinstance(field, messages.FloatField) and
321 isinstance(value, (int, long, basestring))):
322 try:
323 return float(value)
324 except:
325 pass
326
327 elif (isinstance(field, messages.IntegerField) and
328 isinstance(value, basestring)):
329 try:
330 return int(value)
331 except:
332 pass
333
334 return value
335
336 @staticmethod
337 def get_default():
338 """Get default instanceof ProtoJson."""
339 try:
340 return ProtoJson.__default
341 except AttributeError:
342 ProtoJson.__default = ProtoJson()
343 return ProtoJson.__default
344
345 @staticmethod
346 def set_default(protocol):
347 """Set the default instance of ProtoJson.
348
349 Args:
350 protocol: A ProtoJson instance.
351 """
352 if not isinstance(protocol, ProtoJson):
353 raise TypeError('Expected protocol of type ProtoJson')
354 ProtoJson.__default = protocol
355
356 CONTENT_TYPE = ProtoJson.CONTENT_TYPE
357
358 ALTERNATIVE_CONTENT_TYPES = ProtoJson.ALTERNATIVE_CONTENT_TYPES
359
360 encode_message = ProtoJson.get_default().encode_message
361
362 decode_message = ProtoJson.get_default().decode_message
OLDNEW
« no previous file with comments | « gslib/third_party/protorpc/messages.py ('k') | gslib/third_party/protorpc/util.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698