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

Side by Side Diff: third_party/google-endpoints/apitools/base/protorpclite/protojson.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months 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
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 import base64
28 import binascii
29 import logging
30
31 import six
32
33 from apitools.base.protorpclite import message_types
34 from apitools.base.protorpclite import messages
35 from apitools.base.protorpclite import util
36
37 __all__ = [
38 'ALTERNATIVE_CONTENT_TYPES',
39 'CONTENT_TYPE',
40 'MessageJSONEncoder',
41 'encode_message',
42 'decode_message',
43 'ProtoJson',
44 ]
45
46
47 def _load_json_module():
48 """Try to load a valid json module.
49
50 There are more than one json modules that might be installed. They are
51 mostly compatible with one another but some versions may be different.
52 This function attempts to load various json modules in a preferred order.
53 It does a basic check to guess if a loaded version of json is compatible.
54
55 Returns:
56 Compatible json module.
57
58 Raises:
59 ImportError if there are no json modules or the loaded json module is
60 not compatible with ProtoRPC.
61 """
62 first_import_error = None
63 for module_name in ['json',
64 'simplejson']:
65 try:
66 module = __import__(module_name, {}, {}, 'json')
67 if not hasattr(module, 'JSONEncoder'):
68 message = (
69 '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 as err:
76 if not first_import_error:
77 first_import_error = err
78
79 logging.error('Must use valid json library (json or simplejson)')
80 raise first_import_error # pylint:disable=raising-bad-type
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 = (
99 protojson_protocol or ProtoJson.get_default())
100
101 def default(self, value):
102 """Return dictionary instance from a message object.
103
104 Args:
105 value: Value to get dictionary for. If not encodable, will
106 call superclasses default method.
107 """
108 if isinstance(value, messages.Enum):
109 return str(value)
110
111 if six.PY3 and isinstance(value, bytes):
112 return value.decode('utf8')
113
114 if isinstance(value, messages.Message):
115 result = {}
116 for field in value.all_fields():
117 item = value.get_assigned_value(field.name)
118 if item not in (None, [], ()):
119 result[field.name] = (
120 self.__protojson_protocol.encode_field(field, item))
121 # Handle unrecognized fields, so they're included when a message is
122 # decoded then encoded.
123 for unknown_key in value.all_unrecognized_fields():
124 unrecognized_field, _ = value.get_unrecognized_field_info(
125 unknown_key)
126 result[unknown_key] = unrecognized_field
127 return result
128 else:
129 return super(MessageJSONEncoder, self).default(value)
130
131
132 class ProtoJson(object):
133 """ProtoRPC JSON implementation class.
134
135 Implementation of JSON based protocol used for serializing and
136 deserializing message objects. Instances of remote.ProtocolConfig
137 constructor or used with remote.Protocols.add_protocol. See the
138 remote.py module for more details.
139
140 """
141
142 CONTENT_TYPE = 'application/json'
143 ALTERNATIVE_CONTENT_TYPES = [
144 'application/x-javascript',
145 'text/javascript',
146 'text/x-javascript',
147 'text/x-json',
148 'text/json',
149 ]
150
151 def encode_field(self, field, value):
152 """Encode a python field value to a JSON value.
153
154 Args:
155 field: A ProtoRPC field instance.
156 value: A python value supported by field.
157
158 Returns:
159 A JSON serializable value appropriate for field.
160 """
161 if isinstance(field, messages.BytesField):
162 if field.repeated:
163 value = [base64.b64encode(byte) for byte in value]
164 else:
165 value = base64.b64encode(value)
166 elif isinstance(field, message_types.DateTimeField):
167 # DateTimeField stores its data as a RFC 3339 compliant string.
168 if field.repeated:
169 value = [i.isoformat() for i in value]
170 else:
171 value = value.isoformat()
172 return value
173
174 def encode_message(self, message):
175 """Encode Message instance to JSON string.
176
177 Args:
178 Message instance to encode in to JSON string.
179
180 Returns:
181 String encoding of Message instance in protocol JSON format.
182
183 Raises:
184 messages.ValidationError if message is not initialized.
185 """
186 message.check_initialized()
187
188 return json.dumps(message, cls=MessageJSONEncoder,
189 protojson_protocol=self)
190
191 def decode_message(self, message_type, encoded_message):
192 """Merge JSON structure to Message instance.
193
194 Args:
195 message_type: Message to decode data to.
196 encoded_message: JSON encoded version of message.
197
198 Returns:
199 Decoded instance of message_type.
200
201 Raises:
202 ValueError: If encoded_message is not valid JSON.
203 messages.ValidationError if merged message is not initialized.
204 """
205 if not encoded_message.strip():
206 return message_type()
207
208 dictionary = json.loads(encoded_message)
209 message = self.__decode_dictionary(message_type, dictionary)
210 message.check_initialized()
211 return message
212
213 def __find_variant(self, value):
214 """Find the messages.Variant type that describes this value.
215
216 Args:
217 value: The value whose variant type is being determined.
218
219 Returns:
220 The messages.Variant value that best describes value's type,
221 or None if it's a type we don't know how to handle.
222
223 """
224 if isinstance(value, bool):
225 return messages.Variant.BOOL
226 elif isinstance(value, six.integer_types):
227 return messages.Variant.INT64
228 elif isinstance(value, float):
229 return messages.Variant.DOUBLE
230 elif isinstance(value, six.string_types):
231 return messages.Variant.STRING
232 elif isinstance(value, (list, tuple)):
233 # Find the most specific variant that covers all elements.
234 variant_priority = [None,
235 messages.Variant.INT64,
236 messages.Variant.DOUBLE,
237 messages.Variant.STRING]
238 chosen_priority = 0
239 for v in value:
240 variant = self.__find_variant(v)
241 try:
242 priority = variant_priority.index(variant)
243 except IndexError:
244 priority = -1
245 if priority > chosen_priority:
246 chosen_priority = priority
247 return variant_priority[chosen_priority]
248 # Unrecognized type.
249 return None
250
251 def __decode_dictionary(self, message_type, dictionary):
252 """Merge dictionary in to message.
253
254 Args:
255 message: Message to merge dictionary in to.
256 dictionary: Dictionary to extract information from. Dictionary
257 is as parsed from JSON. Nested objects will also be dictionaries.
258 """
259 message = message_type()
260 for key, value in six.iteritems(dictionary):
261 if value is None:
262 try:
263 message.reset(key)
264 except AttributeError:
265 pass # This is an unrecognized field, skip it.
266 continue
267
268 try:
269 field = message.field_by_name(key)
270 except KeyError:
271 # Save unknown values.
272 variant = self.__find_variant(value)
273 if variant:
274 message.set_unrecognized_field(key, value, variant)
275 else:
276 logging.warning(
277 'No variant found for unrecognized field: %s', key)
278 continue
279
280 # Normalize values in to a list.
281 if isinstance(value, list):
282 if not value:
283 continue
284 else:
285 value = [value]
286
287 valid_value = []
288 for item in value:
289 valid_value.append(self.decode_field(field, item))
290
291 if field.repeated:
292 _ = getattr(message, field.name)
293 setattr(message, field.name, valid_value)
294 else:
295 setattr(message, field.name, valid_value[-1])
296 return message
297
298 def decode_field(self, field, value):
299 """Decode a JSON value to a python value.
300
301 Args:
302 field: A ProtoRPC field instance.
303 value: A serialized JSON value.
304
305 Return:
306 A Python value compatible with field.
307 """
308 if isinstance(field, messages.EnumField):
309 try:
310 return field.type(value)
311 except TypeError:
312 raise messages.DecodeError(
313 'Invalid enum value "%s"' % (value or ''))
314
315 elif isinstance(field, messages.BytesField):
316 try:
317 return base64.b64decode(value)
318 except (binascii.Error, TypeError) as err:
319 raise messages.DecodeError('Base64 decoding error: %s' % err)
320
321 elif isinstance(field, message_types.DateTimeField):
322 try:
323 return util.decode_datetime(value)
324 except ValueError as err:
325 raise messages.DecodeError(err)
326
327 elif (isinstance(field, messages.MessageField) and
328 issubclass(field.type, messages.Message)):
329 return self.__decode_dictionary(field.type, value)
330
331 elif (isinstance(field, messages.FloatField) and
332 isinstance(value, (six.integer_types, six.string_types))):
333 try:
334 return float(value)
335 except: # pylint:disable=bare-except
336 pass
337
338 elif (isinstance(field, messages.IntegerField) and
339 isinstance(value, six.string_types)):
340 try:
341 return int(value)
342 except: # pylint:disable=bare-except
343 pass
344
345 return value
346
347 @staticmethod
348 def get_default():
349 """Get default instanceof ProtoJson."""
350 try:
351 return ProtoJson.__default
352 except AttributeError:
353 ProtoJson.__default = ProtoJson()
354 return ProtoJson.__default
355
356 @staticmethod
357 def set_default(protocol):
358 """Set the default instance of ProtoJson.
359
360 Args:
361 protocol: A ProtoJson instance.
362 """
363 if not isinstance(protocol, ProtoJson):
364 raise TypeError('Expected protocol of type ProtoJson')
365 ProtoJson.__default = protocol
366
367 CONTENT_TYPE = ProtoJson.CONTENT_TYPE
368
369 ALTERNATIVE_CONTENT_TYPES = ProtoJson.ALTERNATIVE_CONTENT_TYPES
370
371 encode_message = ProtoJson.get_default().encode_message
372
373 decode_message = ProtoJson.get_default().decode_message
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698