blob: 53c0ad4048591ea7b7f6cce63dff914486f8a9a4 [file] [log] [blame]
Matthew Treinish0db53772013-07-26 10:39:35 -04001# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# Copyright 2011 Justin Santa Barbara
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18'''
19JSON related utilities.
20
21This module provides a few things:
22
23 1) A handy function for getting an object down to something that can be
24 JSON serialized. See to_primitive().
25
26 2) Wrappers around loads() and dumps(). The dumps() wrapper will
27 automatically use to_primitive() for you if needed.
28
29 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
30 is available.
31'''
32
33
34import datetime
35import functools
36import inspect
37import itertools
38import json
Matthew Treinishffa94d62013-09-11 18:09:17 +000039try:
40 import xmlrpclib
41except ImportError:
Sean Daguefc691e32014-01-03 08:51:54 -050042 # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3
43 # however the function and object call signatures
44 # remained the same. This whole try/except block should
45 # be removed and replaced with a call to six.moves once
46 # six 1.4.2 is released. See http://bit.ly/1bqrVzu
47 import xmlrpc.client as xmlrpclib
Matthew Treinish0db53772013-07-26 10:39:35 -040048
Matthew Treinish0db53772013-07-26 10:39:35 -040049import six
50
Matthew Treinishf45528a2013-10-24 20:12:28 +000051from tempest.openstack.common import gettextutils
Matthew Treinishffa94d62013-09-11 18:09:17 +000052from tempest.openstack.common import importutils
Matthew Treinish0db53772013-07-26 10:39:35 -040053from tempest.openstack.common import timeutils
54
Matthew Treinishffa94d62013-09-11 18:09:17 +000055netaddr = importutils.try_import("netaddr")
Matthew Treinish0db53772013-07-26 10:39:35 -040056
57_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
58 inspect.isfunction, inspect.isgeneratorfunction,
59 inspect.isgenerator, inspect.istraceback, inspect.isframe,
60 inspect.iscode, inspect.isbuiltin, inspect.isroutine,
61 inspect.isabstract]
62
Matthew Treinishffa94d62013-09-11 18:09:17 +000063_simple_types = (six.string_types + six.integer_types
64 + (type(None), bool, float))
Matthew Treinish0db53772013-07-26 10:39:35 -040065
66
67def to_primitive(value, convert_instances=False, convert_datetime=True,
68 level=0, max_depth=3):
69 """Convert a complex object into primitives.
70
71 Handy for JSON serialization. We can optionally handle instances,
72 but since this is a recursive function, we could have cyclical
73 data structures.
74
75 To handle cyclical data structures we could track the actual objects
76 visited in a set, but not all objects are hashable. Instead we just
77 track the depth of the object inspections and don't go too deep.
78
79 Therefore, convert_instances=True is lossy ... be aware.
80
81 """
82 # handle obvious types first - order of basic types determined by running
83 # full tests on nova project, resulting in the following counts:
84 # 572754 <type 'NoneType'>
85 # 460353 <type 'int'>
86 # 379632 <type 'unicode'>
87 # 274610 <type 'str'>
88 # 199918 <type 'dict'>
89 # 114200 <type 'datetime.datetime'>
90 # 51817 <type 'bool'>
91 # 26164 <type 'list'>
92 # 6491 <type 'float'>
93 # 283 <type 'tuple'>
94 # 19 <type 'long'>
95 if isinstance(value, _simple_types):
96 return value
97
98 if isinstance(value, datetime.datetime):
99 if convert_datetime:
100 return timeutils.strtime(value)
101 else:
102 return value
103
104 # value of itertools.count doesn't get caught by nasty_type_tests
105 # and results in infinite loop when list(value) is called.
106 if type(value) == itertools.count:
107 return six.text_type(value)
108
109 # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
110 # tests that raise an exception in a mocked method that
111 # has a @wrap_exception with a notifier will fail. If
112 # we up the dependency to 0.5.4 (when it is released) we
113 # can remove this workaround.
114 if getattr(value, '__module__', None) == 'mox':
115 return 'mock'
116
117 if level > max_depth:
118 return '?'
119
120 # The try block may not be necessary after the class check above,
121 # but just in case ...
122 try:
123 recursive = functools.partial(to_primitive,
124 convert_instances=convert_instances,
125 convert_datetime=convert_datetime,
126 level=level,
127 max_depth=max_depth)
128 if isinstance(value, dict):
Sean Daguefc691e32014-01-03 08:51:54 -0500129 return dict((k, recursive(v)) for k, v in six.iteritems(value))
Matthew Treinish0db53772013-07-26 10:39:35 -0400130 elif isinstance(value, (list, tuple)):
131 return [recursive(lv) for lv in value]
132
133 # It's not clear why xmlrpclib created their own DateTime type, but
134 # for our purposes, make it a datetime type which is explicitly
135 # handled
Sean Daguefc691e32014-01-03 08:51:54 -0500136 if isinstance(value, xmlrpclib.DateTime):
Matthew Treinish0db53772013-07-26 10:39:35 -0400137 value = datetime.datetime(*tuple(value.timetuple())[:6])
138
139 if convert_datetime and isinstance(value, datetime.datetime):
140 return timeutils.strtime(value)
Matthew Treinishf45528a2013-10-24 20:12:28 +0000141 elif isinstance(value, gettextutils.Message):
142 return value.data
Matthew Treinish0db53772013-07-26 10:39:35 -0400143 elif hasattr(value, 'iteritems'):
144 return recursive(dict(value.iteritems()), level=level + 1)
145 elif hasattr(value, '__iter__'):
146 return recursive(list(value))
147 elif convert_instances and hasattr(value, '__dict__'):
148 # Likely an instance of something. Watch for cycles.
149 # Ignore class member vars.
150 return recursive(value.__dict__, level=level + 1)
Matthew Treinishffa94d62013-09-11 18:09:17 +0000151 elif netaddr and isinstance(value, netaddr.IPAddress):
Matthew Treinish0db53772013-07-26 10:39:35 -0400152 return six.text_type(value)
153 else:
154 if any(test(value) for test in _nasty_type_tests):
155 return six.text_type(value)
156 return value
157 except TypeError:
158 # Class objects are tricky since they may define something like
159 # __iter__ defined but it isn't callable as list().
160 return six.text_type(value)
161
162
163def dumps(value, default=to_primitive, **kwargs):
164 return json.dumps(value, default=default, **kwargs)
165
166
167def loads(s):
168 return json.loads(s)
169
170
171def load(s):
172 return json.load(s)
173
174
175try:
176 import anyjson
177except ImportError:
178 pass
179else:
180 anyjson._modules.append((__name__, 'dumps', TypeError,
181 'loads', ValueError, 'load'))
182 anyjson.force_implementation(__name__)