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