blob: c568a0621b575fc742e022731ff45fd91060126a [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 Treinishffa94d62013-09-11 18:09:17 +000049from tempest.openstack.common import importutils
Matthew Treinish0db53772013-07-26 10:39:35 -040050from tempest.openstack.common import timeutils
51
Matthew Treinishffa94d62013-09-11 18:09:17 +000052netaddr = importutils.try_import("netaddr")
Matthew Treinish0db53772013-07-26 10:39:35 -040053
54_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
55 inspect.isfunction, inspect.isgeneratorfunction,
56 inspect.isgenerator, inspect.istraceback, inspect.isframe,
57 inspect.iscode, inspect.isbuiltin, inspect.isroutine,
58 inspect.isabstract]
59
Matthew Treinishffa94d62013-09-11 18:09:17 +000060_simple_types = (six.string_types + six.integer_types
61 + (type(None), bool, float))
Matthew Treinish0db53772013-07-26 10:39:35 -040062
63
64def to_primitive(value, convert_instances=False, convert_datetime=True,
65 level=0, max_depth=3):
66 """Convert a complex object into primitives.
67
68 Handy for JSON serialization. We can optionally handle instances,
69 but since this is a recursive function, we could have cyclical
70 data structures.
71
72 To handle cyclical data structures we could track the actual objects
73 visited in a set, but not all objects are hashable. Instead we just
74 track the depth of the object inspections and don't go too deep.
75
76 Therefore, convert_instances=True is lossy ... be aware.
77
78 """
79 # handle obvious types first - order of basic types determined by running
80 # full tests on nova project, resulting in the following counts:
81 # 572754 <type 'NoneType'>
82 # 460353 <type 'int'>
83 # 379632 <type 'unicode'>
84 # 274610 <type 'str'>
85 # 199918 <type 'dict'>
86 # 114200 <type 'datetime.datetime'>
87 # 51817 <type 'bool'>
88 # 26164 <type 'list'>
89 # 6491 <type 'float'>
90 # 283 <type 'tuple'>
91 # 19 <type 'long'>
92 if isinstance(value, _simple_types):
93 return value
94
95 if isinstance(value, datetime.datetime):
96 if convert_datetime:
97 return timeutils.strtime(value)
98 else:
99 return value
100
101 # value of itertools.count doesn't get caught by nasty_type_tests
102 # and results in infinite loop when list(value) is called.
103 if type(value) == itertools.count:
104 return six.text_type(value)
105
106 # FIXME(vish): Workaround for LP bug 852095. Without this workaround,
107 # tests that raise an exception in a mocked method that
108 # has a @wrap_exception with a notifier will fail. If
109 # we up the dependency to 0.5.4 (when it is released) we
110 # can remove this workaround.
111 if getattr(value, '__module__', None) == 'mox':
112 return 'mock'
113
114 if level > max_depth:
115 return '?'
116
117 # The try block may not be necessary after the class check above,
118 # but just in case ...
119 try:
120 recursive = functools.partial(to_primitive,
121 convert_instances=convert_instances,
122 convert_datetime=convert_datetime,
123 level=level,
124 max_depth=max_depth)
125 if isinstance(value, dict):
126 return dict((k, recursive(v)) for k, v in value.iteritems())
127 elif isinstance(value, (list, tuple)):
128 return [recursive(lv) for lv in value]
129
130 # It's not clear why xmlrpclib created their own DateTime type, but
131 # for our purposes, make it a datetime type which is explicitly
132 # handled
Matthew Treinishffa94d62013-09-11 18:09:17 +0000133 if xmlrpclib and isinstance(value, xmlrpclib.DateTime):
Matthew Treinish0db53772013-07-26 10:39:35 -0400134 value = datetime.datetime(*tuple(value.timetuple())[:6])
135
136 if convert_datetime and isinstance(value, datetime.datetime):
137 return timeutils.strtime(value)
138 elif hasattr(value, 'iteritems'):
139 return recursive(dict(value.iteritems()), level=level + 1)
140 elif hasattr(value, '__iter__'):
141 return recursive(list(value))
142 elif convert_instances and hasattr(value, '__dict__'):
143 # Likely an instance of something. Watch for cycles.
144 # Ignore class member vars.
145 return recursive(value.__dict__, level=level + 1)
Matthew Treinishffa94d62013-09-11 18:09:17 +0000146 elif netaddr and isinstance(value, netaddr.IPAddress):
Matthew Treinish0db53772013-07-26 10:39:35 -0400147 return six.text_type(value)
148 else:
149 if any(test(value) for test in _nasty_type_tests):
150 return six.text_type(value)
151 return value
152 except TypeError:
153 # Class objects are tricky since they may define something like
154 # __iter__ defined but it isn't callable as list().
155 return six.text_type(value)
156
157
158def dumps(value, default=to_primitive, **kwargs):
159 return json.dumps(value, default=default, **kwargs)
160
161
162def loads(s):
163 return json.loads(s)
164
165
166def load(s):
167 return json.load(s)
168
169
170try:
171 import anyjson
172except ImportError:
173 pass
174else:
175 anyjson._modules.append((__name__, 'dumps', TypeError,
176 'loads', ValueError, 'load'))
177 anyjson.force_implementation(__name__)