Move files out of the namespace package
Move the public API out of oslo.serialization to oslo_serialization. Retain the ability to import from the old namespace package for backwards compatibility for this release cycle. bp/drop-namespace-packages Change-Id: Ic60f809ea00ac77b0753556c6fd00b97e64d57ff
This commit is contained in:
parent
437eaf8db5
commit
e8deb08b7f
@ -5,5 +5,5 @@
|
|||||||
jsonutils
|
jsonutils
|
||||||
=========
|
=========
|
||||||
|
|
||||||
.. automodule:: oslo.serialization.jsonutils
|
.. automodule:: oslo_serialization.jsonutils
|
||||||
:members:
|
:members:
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated():
|
||||||
|
new_name = __name__.replace('.', '_')
|
||||||
|
warnings.warn(
|
||||||
|
('The oslo namespace package is deprecated. Please use %s instead.' %
|
||||||
|
new_name),
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
deprecated()
|
@ -1,8 +1,3 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# Copyright 2011 Justin Santa Barbara
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
@ -15,221 +10,4 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
'''
|
from oslo_serialization.jsonutils import * # noqa
|
||||||
JSON related utilities.
|
|
||||||
|
|
||||||
This module provides a few things:
|
|
||||||
|
|
||||||
1) A handy function for getting an object down to something that can be
|
|
||||||
JSON serialized. See to_primitive().
|
|
||||||
|
|
||||||
2) Wrappers around loads() and dumps(). The dumps() wrapper will
|
|
||||||
automatically use to_primitive() for you if needed.
|
|
||||||
|
|
||||||
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
|
|
||||||
is available.
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
import codecs
|
|
||||||
import datetime
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
import itertools
|
|
||||||
import sys
|
|
||||||
|
|
||||||
is_simplejson = False
|
|
||||||
if sys.version_info < (2, 7):
|
|
||||||
# On Python <= 2.6, json module is not C boosted, so try to use
|
|
||||||
# simplejson module if available
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
# NOTE(mriedem): Make sure we have a new enough version of simplejson
|
|
||||||
# to support the namedobject_as_tuple argument. This can be removed
|
|
||||||
# in the Kilo release when python 2.6 support is dropped.
|
|
||||||
if 'namedtuple_as_object' in inspect.getargspec(json.dumps).args:
|
|
||||||
is_simplejson = True
|
|
||||||
else:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
import json
|
|
||||||
else:
|
|
||||||
import json
|
|
||||||
|
|
||||||
import six
|
|
||||||
import six.moves.xmlrpc_client as xmlrpclib
|
|
||||||
|
|
||||||
from oslo.utils import encodeutils
|
|
||||||
from oslo.utils import importutils
|
|
||||||
from oslo.utils import timeutils
|
|
||||||
|
|
||||||
netaddr = importutils.try_import("netaddr")
|
|
||||||
|
|
||||||
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
|
|
||||||
inspect.isfunction, inspect.isgeneratorfunction,
|
|
||||||
inspect.isgenerator, inspect.istraceback, inspect.isframe,
|
|
||||||
inspect.iscode, inspect.isbuiltin, inspect.isroutine,
|
|
||||||
inspect.isabstract]
|
|
||||||
|
|
||||||
_simple_types = (six.string_types + six.integer_types
|
|
||||||
+ (type(None), bool, float))
|
|
||||||
|
|
||||||
|
|
||||||
def to_primitive(value, convert_instances=False, convert_datetime=True,
|
|
||||||
level=0, max_depth=3):
|
|
||||||
"""Convert a complex object into primitives.
|
|
||||||
|
|
||||||
Handy for JSON serialization. We can optionally handle instances,
|
|
||||||
but since this is a recursive function, we could have cyclical
|
|
||||||
data structures.
|
|
||||||
|
|
||||||
To handle cyclical data structures we could track the actual objects
|
|
||||||
visited in a set, but not all objects are hashable. Instead we just
|
|
||||||
track the depth of the object inspections and don't go too deep.
|
|
||||||
|
|
||||||
Therefore, convert_instances=True is lossy ... be aware.
|
|
||||||
"""
|
|
||||||
# handle obvious types first - order of basic types determined by running
|
|
||||||
# full tests on nova project, resulting in the following counts:
|
|
||||||
# 572754 <type 'NoneType'>
|
|
||||||
# 460353 <type 'int'>
|
|
||||||
# 379632 <type 'unicode'>
|
|
||||||
# 274610 <type 'str'>
|
|
||||||
# 199918 <type 'dict'>
|
|
||||||
# 114200 <type 'datetime.datetime'>
|
|
||||||
# 51817 <type 'bool'>
|
|
||||||
# 26164 <type 'list'>
|
|
||||||
# 6491 <type 'float'>
|
|
||||||
# 283 <type 'tuple'>
|
|
||||||
# 19 <type 'long'>
|
|
||||||
if isinstance(value, _simple_types):
|
|
||||||
return value
|
|
||||||
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
if convert_datetime:
|
|
||||||
return timeutils.strtime(value)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
# value of itertools.count doesn't get caught by nasty_type_tests
|
|
||||||
# and results in infinite loop when list(value) is called.
|
|
||||||
if type(value) == itertools.count:
|
|
||||||
return six.text_type(value)
|
|
||||||
|
|
||||||
# FIXME(vish): Workaround for LP bug 852095. Without this workaround,
|
|
||||||
# tests that raise an exception in a mocked method that
|
|
||||||
# has a @wrap_exception with a notifier will fail. If
|
|
||||||
# we up the dependency to 0.5.4 (when it is released) we
|
|
||||||
# can remove this workaround.
|
|
||||||
if getattr(value, '__module__', None) == 'mox':
|
|
||||||
return 'mock'
|
|
||||||
|
|
||||||
if level > max_depth:
|
|
||||||
return '?'
|
|
||||||
|
|
||||||
# The try block may not be necessary after the class check above,
|
|
||||||
# but just in case ...
|
|
||||||
try:
|
|
||||||
recursive = functools.partial(to_primitive,
|
|
||||||
convert_instances=convert_instances,
|
|
||||||
convert_datetime=convert_datetime,
|
|
||||||
level=level,
|
|
||||||
max_depth=max_depth)
|
|
||||||
if isinstance(value, dict):
|
|
||||||
return dict((k, recursive(v)) for k, v in six.iteritems(value))
|
|
||||||
elif isinstance(value, (list, tuple)):
|
|
||||||
return [recursive(lv) for lv in value]
|
|
||||||
|
|
||||||
# It's not clear why xmlrpclib created their own DateTime type, but
|
|
||||||
# for our purposes, make it a datetime type which is explicitly
|
|
||||||
# handled
|
|
||||||
if isinstance(value, xmlrpclib.DateTime):
|
|
||||||
value = datetime.datetime(*tuple(value.timetuple())[:6])
|
|
||||||
|
|
||||||
if convert_datetime and isinstance(value, datetime.datetime):
|
|
||||||
return timeutils.strtime(value)
|
|
||||||
elif hasattr(value, 'iteritems'):
|
|
||||||
return recursive(dict(value.iteritems()), level=level + 1)
|
|
||||||
elif hasattr(value, '__iter__'):
|
|
||||||
return recursive(list(value))
|
|
||||||
elif convert_instances and hasattr(value, '__dict__'):
|
|
||||||
# Likely an instance of something. Watch for cycles.
|
|
||||||
# Ignore class member vars.
|
|
||||||
return recursive(value.__dict__, level=level + 1)
|
|
||||||
elif netaddr and isinstance(value, netaddr.IPAddress):
|
|
||||||
return six.text_type(value)
|
|
||||||
else:
|
|
||||||
if any(test(value) for test in _nasty_type_tests):
|
|
||||||
return six.text_type(value)
|
|
||||||
return value
|
|
||||||
except TypeError:
|
|
||||||
# Class objects are tricky since they may define something like
|
|
||||||
# __iter__ defined but it isn't callable as list().
|
|
||||||
return six.text_type(value)
|
|
||||||
|
|
||||||
|
|
||||||
JSONEncoder = json.JSONEncoder
|
|
||||||
JSONDecoder = json.JSONDecoder
|
|
||||||
|
|
||||||
|
|
||||||
def dumps(obj, default=to_primitive, **kwargs):
|
|
||||||
"""Serialize ``obj`` to a JSON formatted ``str``.
|
|
||||||
|
|
||||||
:param obj: object to be serialized
|
|
||||||
:param default: function that returns a serializable version of an object
|
|
||||||
:param kwargs: extra named parameters, please see documentation \
|
|
||||||
of `json.dumps <https://docs.python.org/2/library/json.html#basic-usage>`_
|
|
||||||
:returns: json formatted string
|
|
||||||
"""
|
|
||||||
if is_simplejson:
|
|
||||||
kwargs['namedtuple_as_object'] = False
|
|
||||||
return json.dumps(obj, default=default, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def dump(obj, fp, *args, **kwargs):
|
|
||||||
"""Serialize ``obj`` as a JSON formatted stream to ``fp``
|
|
||||||
|
|
||||||
:param obj: object to be serialized
|
|
||||||
:param fp: a ``.write()``-supporting file-like object
|
|
||||||
:param args: extra arguments, please see documentation \
|
|
||||||
of `json.dump <https://docs.python.org/2/library/json.html#basic-usage>`_
|
|
||||||
:param kwargs: extra named parameters, please see documentation \
|
|
||||||
of `json.dump <https://docs.python.org/2/library/json.html#basic-usage>`_
|
|
||||||
"""
|
|
||||||
if is_simplejson:
|
|
||||||
kwargs['namedtuple_as_object'] = False
|
|
||||||
return json.dump(obj, fp, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def loads(s, encoding='utf-8', **kwargs):
|
|
||||||
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
|
|
||||||
|
|
||||||
:param s: string to deserialize
|
|
||||||
:param encoding: encoding used to interpret the string
|
|
||||||
:param kwargs: extra named parameters, please see documentation \
|
|
||||||
of `json.loads <https://docs.python.org/2/library/json.html#basic-usage>`_
|
|
||||||
:returns: python object
|
|
||||||
"""
|
|
||||||
return json.loads(encodeutils.safe_decode(s, encoding), **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def load(fp, encoding='utf-8', **kwargs):
|
|
||||||
"""Deserialize ``fp`` to a Python object.
|
|
||||||
|
|
||||||
:param fp: a ``.read()`` -supporting file-like object
|
|
||||||
:param encoding: encoding used to interpret the string
|
|
||||||
:param kwargs: extra named parameters, please see documentation \
|
|
||||||
of `json.loads <https://docs.python.org/2/library/json.html#basic-usage>`_
|
|
||||||
:returns: python object
|
|
||||||
"""
|
|
||||||
return json.load(codecs.getreader(encoding)(fp), **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import anyjson
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
anyjson._modules.append((__name__, 'dumps', TypeError,
|
|
||||||
'loads', ValueError, 'load'))
|
|
||||||
anyjson.force_implementation(__name__)
|
|
||||||
|
0
oslo_serialization/__init__.py
Normal file
0
oslo_serialization/__init__.py
Normal file
235
oslo_serialization/jsonutils.py
Normal file
235
oslo_serialization/jsonutils.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# Copyright 2011 Justin Santa Barbara
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
'''
|
||||||
|
JSON related utilities.
|
||||||
|
|
||||||
|
This module provides a few things:
|
||||||
|
|
||||||
|
1) A handy function for getting an object down to something that can be
|
||||||
|
JSON serialized. See to_primitive().
|
||||||
|
|
||||||
|
2) Wrappers around loads() and dumps(). The dumps() wrapper will
|
||||||
|
automatically use to_primitive() for you if needed.
|
||||||
|
|
||||||
|
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson
|
||||||
|
is available.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import datetime
|
||||||
|
import functools
|
||||||
|
import inspect
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
is_simplejson = False
|
||||||
|
if sys.version_info < (2, 7):
|
||||||
|
# On Python <= 2.6, json module is not C boosted, so try to use
|
||||||
|
# simplejson module if available
|
||||||
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
# NOTE(mriedem): Make sure we have a new enough version of simplejson
|
||||||
|
# to support the namedobject_as_tuple argument. This can be removed
|
||||||
|
# in the Kilo release when python 2.6 support is dropped.
|
||||||
|
if 'namedtuple_as_object' in inspect.getargspec(json.dumps).args:
|
||||||
|
is_simplejson = True
|
||||||
|
else:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import json
|
||||||
|
else:
|
||||||
|
import json
|
||||||
|
|
||||||
|
import six
|
||||||
|
import six.moves.xmlrpc_client as xmlrpclib
|
||||||
|
|
||||||
|
from oslo.utils import encodeutils
|
||||||
|
from oslo.utils import importutils
|
||||||
|
from oslo.utils import timeutils
|
||||||
|
|
||||||
|
netaddr = importutils.try_import("netaddr")
|
||||||
|
|
||||||
|
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod,
|
||||||
|
inspect.isfunction, inspect.isgeneratorfunction,
|
||||||
|
inspect.isgenerator, inspect.istraceback, inspect.isframe,
|
||||||
|
inspect.iscode, inspect.isbuiltin, inspect.isroutine,
|
||||||
|
inspect.isabstract]
|
||||||
|
|
||||||
|
_simple_types = (six.string_types + six.integer_types
|
||||||
|
+ (type(None), bool, float))
|
||||||
|
|
||||||
|
|
||||||
|
def to_primitive(value, convert_instances=False, convert_datetime=True,
|
||||||
|
level=0, max_depth=3):
|
||||||
|
"""Convert a complex object into primitives.
|
||||||
|
|
||||||
|
Handy for JSON serialization. We can optionally handle instances,
|
||||||
|
but since this is a recursive function, we could have cyclical
|
||||||
|
data structures.
|
||||||
|
|
||||||
|
To handle cyclical data structures we could track the actual objects
|
||||||
|
visited in a set, but not all objects are hashable. Instead we just
|
||||||
|
track the depth of the object inspections and don't go too deep.
|
||||||
|
|
||||||
|
Therefore, convert_instances=True is lossy ... be aware.
|
||||||
|
"""
|
||||||
|
# handle obvious types first - order of basic types determined by running
|
||||||
|
# full tests on nova project, resulting in the following counts:
|
||||||
|
# 572754 <type 'NoneType'>
|
||||||
|
# 460353 <type 'int'>
|
||||||
|
# 379632 <type 'unicode'>
|
||||||
|
# 274610 <type 'str'>
|
||||||
|
# 199918 <type 'dict'>
|
||||||
|
# 114200 <type 'datetime.datetime'>
|
||||||
|
# 51817 <type 'bool'>
|
||||||
|
# 26164 <type 'list'>
|
||||||
|
# 6491 <type 'float'>
|
||||||
|
# 283 <type 'tuple'>
|
||||||
|
# 19 <type 'long'>
|
||||||
|
if isinstance(value, _simple_types):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
if convert_datetime:
|
||||||
|
return timeutils.strtime(value)
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# value of itertools.count doesn't get caught by nasty_type_tests
|
||||||
|
# and results in infinite loop when list(value) is called.
|
||||||
|
if type(value) == itertools.count:
|
||||||
|
return six.text_type(value)
|
||||||
|
|
||||||
|
# FIXME(vish): Workaround for LP bug 852095. Without this workaround,
|
||||||
|
# tests that raise an exception in a mocked method that
|
||||||
|
# has a @wrap_exception with a notifier will fail. If
|
||||||
|
# we up the dependency to 0.5.4 (when it is released) we
|
||||||
|
# can remove this workaround.
|
||||||
|
if getattr(value, '__module__', None) == 'mox':
|
||||||
|
return 'mock'
|
||||||
|
|
||||||
|
if level > max_depth:
|
||||||
|
return '?'
|
||||||
|
|
||||||
|
# The try block may not be necessary after the class check above,
|
||||||
|
# but just in case ...
|
||||||
|
try:
|
||||||
|
recursive = functools.partial(to_primitive,
|
||||||
|
convert_instances=convert_instances,
|
||||||
|
convert_datetime=convert_datetime,
|
||||||
|
level=level,
|
||||||
|
max_depth=max_depth)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return dict((k, recursive(v)) for k, v in six.iteritems(value))
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
return [recursive(lv) for lv in value]
|
||||||
|
|
||||||
|
# It's not clear why xmlrpclib created their own DateTime type, but
|
||||||
|
# for our purposes, make it a datetime type which is explicitly
|
||||||
|
# handled
|
||||||
|
if isinstance(value, xmlrpclib.DateTime):
|
||||||
|
value = datetime.datetime(*tuple(value.timetuple())[:6])
|
||||||
|
|
||||||
|
if convert_datetime and isinstance(value, datetime.datetime):
|
||||||
|
return timeutils.strtime(value)
|
||||||
|
elif hasattr(value, 'iteritems'):
|
||||||
|
return recursive(dict(value.iteritems()), level=level + 1)
|
||||||
|
elif hasattr(value, '__iter__'):
|
||||||
|
return recursive(list(value))
|
||||||
|
elif convert_instances and hasattr(value, '__dict__'):
|
||||||
|
# Likely an instance of something. Watch for cycles.
|
||||||
|
# Ignore class member vars.
|
||||||
|
return recursive(value.__dict__, level=level + 1)
|
||||||
|
elif netaddr and isinstance(value, netaddr.IPAddress):
|
||||||
|
return six.text_type(value)
|
||||||
|
else:
|
||||||
|
if any(test(value) for test in _nasty_type_tests):
|
||||||
|
return six.text_type(value)
|
||||||
|
return value
|
||||||
|
except TypeError:
|
||||||
|
# Class objects are tricky since they may define something like
|
||||||
|
# __iter__ defined but it isn't callable as list().
|
||||||
|
return six.text_type(value)
|
||||||
|
|
||||||
|
|
||||||
|
JSONEncoder = json.JSONEncoder
|
||||||
|
JSONDecoder = json.JSONDecoder
|
||||||
|
|
||||||
|
|
||||||
|
def dumps(obj, default=to_primitive, **kwargs):
|
||||||
|
"""Serialize ``obj`` to a JSON formatted ``str``.
|
||||||
|
|
||||||
|
:param obj: object to be serialized
|
||||||
|
:param default: function that returns a serializable version of an object
|
||||||
|
:param kwargs: extra named parameters, please see documentation \
|
||||||
|
of `json.dumps <https://docs.python.org/2/library/json.html#basic-usage>`_
|
||||||
|
:returns: json formatted string
|
||||||
|
"""
|
||||||
|
if is_simplejson:
|
||||||
|
kwargs['namedtuple_as_object'] = False
|
||||||
|
return json.dumps(obj, default=default, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dump(obj, fp, *args, **kwargs):
|
||||||
|
"""Serialize ``obj`` as a JSON formatted stream to ``fp``
|
||||||
|
|
||||||
|
:param obj: object to be serialized
|
||||||
|
:param fp: a ``.write()``-supporting file-like object
|
||||||
|
:param args: extra arguments, please see documentation \
|
||||||
|
of `json.dump <https://docs.python.org/2/library/json.html#basic-usage>`_
|
||||||
|
:param kwargs: extra named parameters, please see documentation \
|
||||||
|
of `json.dump <https://docs.python.org/2/library/json.html#basic-usage>`_
|
||||||
|
"""
|
||||||
|
if is_simplejson:
|
||||||
|
kwargs['namedtuple_as_object'] = False
|
||||||
|
return json.dump(obj, fp, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def loads(s, encoding='utf-8', **kwargs):
|
||||||
|
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
|
||||||
|
|
||||||
|
:param s: string to deserialize
|
||||||
|
:param encoding: encoding used to interpret the string
|
||||||
|
:param kwargs: extra named parameters, please see documentation \
|
||||||
|
of `json.loads <https://docs.python.org/2/library/json.html#basic-usage>`_
|
||||||
|
:returns: python object
|
||||||
|
"""
|
||||||
|
return json.loads(encodeutils.safe_decode(s, encoding), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def load(fp, encoding='utf-8', **kwargs):
|
||||||
|
"""Deserialize ``fp`` to a Python object.
|
||||||
|
|
||||||
|
:param fp: a ``.read()`` -supporting file-like object
|
||||||
|
:param encoding: encoding used to interpret the string
|
||||||
|
:param kwargs: extra named parameters, please see documentation \
|
||||||
|
of `json.loads <https://docs.python.org/2/library/json.html#basic-usage>`_
|
||||||
|
:returns: python object
|
||||||
|
"""
|
||||||
|
return json.load(codecs.getreader(encoding)(fp), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import anyjson
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
anyjson._modules.append((__name__, 'dumps', TypeError,
|
||||||
|
'loads', ValueError, 'load'))
|
||||||
|
anyjson.force_implementation(__name__)
|
0
oslo_serialization/tests/__init__.py
Normal file
0
oslo_serialization/tests/__init__.py
Normal file
275
oslo_serialization/tests/test_jsonutils.py
Normal file
275
oslo_serialization/tests/test_jsonutils.py
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
# Copyright 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import netaddr
|
||||||
|
from oslotest import base as test_base
|
||||||
|
import simplejson
|
||||||
|
import six
|
||||||
|
import six.moves.xmlrpc_client as xmlrpclib
|
||||||
|
|
||||||
|
from oslo.i18n import fixture
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
|
||||||
|
class JSONUtilsTestMixin(object):
|
||||||
|
|
||||||
|
json_impl = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(JSONUtilsTestMixin, self).setUp()
|
||||||
|
self.json_patcher = mock.patch.multiple(
|
||||||
|
jsonutils, json=self.json_impl,
|
||||||
|
is_simplejson=self.json_impl is simplejson)
|
||||||
|
self.json_impl_mock = self.json_patcher.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.json_patcher.stop()
|
||||||
|
super(JSONUtilsTestMixin, self).tearDown()
|
||||||
|
|
||||||
|
def test_dumps(self):
|
||||||
|
self.assertEqual('{"a": "b"}', jsonutils.dumps({'a': 'b'}))
|
||||||
|
|
||||||
|
def test_dumps_namedtuple(self):
|
||||||
|
n = collections.namedtuple("foo", "bar baz")(1, 2)
|
||||||
|
self.assertEqual('[1, 2]', jsonutils.dumps(n))
|
||||||
|
|
||||||
|
def test_dump(self):
|
||||||
|
expected = '{"a": "b"}'
|
||||||
|
json_dict = {'a': 'b'}
|
||||||
|
|
||||||
|
fp = six.StringIO()
|
||||||
|
jsonutils.dump(json_dict, fp)
|
||||||
|
|
||||||
|
self.assertEqual(expected, fp.getvalue())
|
||||||
|
|
||||||
|
def test_dump_namedtuple(self):
|
||||||
|
expected = '[1, 2]'
|
||||||
|
json_dict = collections.namedtuple("foo", "bar baz")(1, 2)
|
||||||
|
|
||||||
|
fp = six.StringIO()
|
||||||
|
jsonutils.dump(json_dict, fp)
|
||||||
|
|
||||||
|
self.assertEqual(expected, fp.getvalue())
|
||||||
|
|
||||||
|
def test_loads(self):
|
||||||
|
self.assertEqual({'a': 'b'}, jsonutils.loads('{"a": "b"}'))
|
||||||
|
|
||||||
|
def test_loads_unicode(self):
|
||||||
|
self.assertIsInstance(jsonutils.loads(b'"foo"'), six.text_type)
|
||||||
|
self.assertIsInstance(jsonutils.loads(u'"foo"'), six.text_type)
|
||||||
|
|
||||||
|
# 'test' in Ukrainian
|
||||||
|
i18n_str_unicode = u'"\u0442\u0435\u0441\u0442"'
|
||||||
|
self.assertIsInstance(jsonutils.loads(i18n_str_unicode), six.text_type)
|
||||||
|
|
||||||
|
i18n_str = i18n_str_unicode.encode('utf-8')
|
||||||
|
self.assertIsInstance(jsonutils.loads(i18n_str), six.text_type)
|
||||||
|
|
||||||
|
def test_loads_with_kwargs(self):
|
||||||
|
jsontext = u'{"foo": 3}'
|
||||||
|
result = jsonutils.loads(jsontext, parse_int=lambda x: 5)
|
||||||
|
self.assertEqual(5, result['foo'])
|
||||||
|
|
||||||
|
def test_load(self):
|
||||||
|
|
||||||
|
jsontext = u'{"a": "\u0442\u044d\u0441\u0442"}'
|
||||||
|
expected = {u'a': u'\u0442\u044d\u0441\u0442'}
|
||||||
|
|
||||||
|
for encoding in ('utf-8', 'cp1251'):
|
||||||
|
fp = six.BytesIO(jsontext.encode(encoding))
|
||||||
|
result = jsonutils.load(fp, encoding=encoding)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
for key, val in result.items():
|
||||||
|
self.assertIsInstance(key, six.text_type)
|
||||||
|
self.assertIsInstance(val, six.text_type)
|
||||||
|
|
||||||
|
|
||||||
|
class JSONUtilsTestJson(JSONUtilsTestMixin, test_base.BaseTestCase):
|
||||||
|
json_impl = json
|
||||||
|
|
||||||
|
|
||||||
|
class JSONUtilsTestSimpleJson(JSONUtilsTestMixin, test_base.BaseTestCase):
|
||||||
|
json_impl = simplejson
|
||||||
|
|
||||||
|
|
||||||
|
class ToPrimitiveTestCase(test_base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ToPrimitiveTestCase, self).setUp()
|
||||||
|
self.trans_fixture = self.useFixture(fixture.Translation())
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
self.assertEqual(jsonutils.to_primitive([1, 2, 3]), [1, 2, 3])
|
||||||
|
|
||||||
|
def test_empty_list(self):
|
||||||
|
self.assertEqual(jsonutils.to_primitive([]), [])
|
||||||
|
|
||||||
|
def test_tuple(self):
|
||||||
|
self.assertEqual(jsonutils.to_primitive((1, 2, 3)), [1, 2, 3])
|
||||||
|
|
||||||
|
def test_dict(self):
|
||||||
|
self.assertEqual(jsonutils.to_primitive(dict(a=1, b=2, c=3)),
|
||||||
|
dict(a=1, b=2, c=3))
|
||||||
|
|
||||||
|
def test_empty_dict(self):
|
||||||
|
self.assertEqual(jsonutils.to_primitive({}), {})
|
||||||
|
|
||||||
|
def test_datetime(self):
|
||||||
|
x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7)
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x),
|
||||||
|
'1920-02-03T04:05:06.000007')
|
||||||
|
|
||||||
|
def test_datetime_preserve(self):
|
||||||
|
x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7)
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x, convert_datetime=False), x)
|
||||||
|
|
||||||
|
def test_DateTime(self):
|
||||||
|
x = xmlrpclib.DateTime()
|
||||||
|
x.decode("19710203T04:05:06")
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x),
|
||||||
|
'1971-02-03T04:05:06.000000')
|
||||||
|
|
||||||
|
def test_iter(self):
|
||||||
|
class IterClass(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.data = [1, 2, 3, 4, 5]
|
||||||
|
self.index = 0
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self.index == len(self.data):
|
||||||
|
raise StopIteration
|
||||||
|
self.index = self.index + 1
|
||||||
|
return self.data[self.index - 1]
|
||||||
|
__next__ = next
|
||||||
|
|
||||||
|
x = IterClass()
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x), [1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
def test_iteritems(self):
|
||||||
|
class IterItemsClass(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.data = dict(a=1, b=2, c=3).items()
|
||||||
|
self.index = 0
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
x = IterItemsClass()
|
||||||
|
p = jsonutils.to_primitive(x)
|
||||||
|
self.assertEqual(p, {'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
|
def test_iteritems_with_cycle(self):
|
||||||
|
class IterItemsClass(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.data = dict(a=1, b=2, c=3)
|
||||||
|
self.index = 0
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
return self.data.items()
|
||||||
|
|
||||||
|
x = IterItemsClass()
|
||||||
|
x2 = IterItemsClass()
|
||||||
|
x.data['other'] = x2
|
||||||
|
x2.data['other'] = x
|
||||||
|
|
||||||
|
# If the cycle isn't caught, to_primitive() will eventually result in
|
||||||
|
# an exception due to excessive recursion depth.
|
||||||
|
jsonutils.to_primitive(x)
|
||||||
|
|
||||||
|
def test_instance(self):
|
||||||
|
class MysteryClass(object):
|
||||||
|
a = 10
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.b = 1
|
||||||
|
|
||||||
|
x = MysteryClass()
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x, convert_instances=True),
|
||||||
|
dict(b=1))
|
||||||
|
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x), x)
|
||||||
|
|
||||||
|
def test_typeerror(self):
|
||||||
|
x = bytearray # Class, not instance
|
||||||
|
if six.PY3:
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x), u"<class 'bytearray'>")
|
||||||
|
else:
|
||||||
|
self.assertEqual(jsonutils.to_primitive(x), u"<type 'bytearray'>")
|
||||||
|
|
||||||
|
def test_nasties(self):
|
||||||
|
def foo():
|
||||||
|
pass
|
||||||
|
x = [datetime, foo, dir]
|
||||||
|
ret = jsonutils.to_primitive(x)
|
||||||
|
self.assertEqual(len(ret), 3)
|
||||||
|
self.assertTrue(ret[0].startswith(u"<module 'datetime' from ") or
|
||||||
|
ret[0].startswith(u"<module 'datetime' (built-in)"))
|
||||||
|
if six.PY3:
|
||||||
|
self.assertTrue(ret[1].startswith(
|
||||||
|
'<function ToPrimitiveTestCase.test_nasties.<locals>.foo at 0x'
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
self.assertTrue(ret[1].startswith('<function foo at 0x'))
|
||||||
|
self.assertEqual(ret[2], '<built-in function dir>')
|
||||||
|
|
||||||
|
def test_depth(self):
|
||||||
|
class LevelsGenerator(object):
|
||||||
|
def __init__(self, levels):
|
||||||
|
self._levels = levels
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
if self._levels == 0:
|
||||||
|
return iter([])
|
||||||
|
else:
|
||||||
|
return iter([(0, LevelsGenerator(self._levels - 1))])
|
||||||
|
|
||||||
|
l4_obj = LevelsGenerator(4)
|
||||||
|
|
||||||
|
json_l2 = {0: {0: '?'}}
|
||||||
|
json_l3 = {0: {0: {0: '?'}}}
|
||||||
|
json_l4 = {0: {0: {0: {0: '?'}}}}
|
||||||
|
|
||||||
|
ret = jsonutils.to_primitive(l4_obj, max_depth=2)
|
||||||
|
self.assertEqual(ret, json_l2)
|
||||||
|
|
||||||
|
ret = jsonutils.to_primitive(l4_obj, max_depth=3)
|
||||||
|
self.assertEqual(ret, json_l3)
|
||||||
|
|
||||||
|
ret = jsonutils.to_primitive(l4_obj, max_depth=4)
|
||||||
|
self.assertEqual(ret, json_l4)
|
||||||
|
|
||||||
|
def test_ipaddr(self):
|
||||||
|
thing = {'ip_addr': netaddr.IPAddress('1.2.3.4')}
|
||||||
|
ret = jsonutils.to_primitive(thing)
|
||||||
|
self.assertEqual({'ip_addr': '1.2.3.4'}, ret)
|
||||||
|
|
||||||
|
def test_message_with_param(self):
|
||||||
|
msg = self.trans_fixture.lazy('A message with param: %s')
|
||||||
|
msg = msg % 'test_domain'
|
||||||
|
ret = jsonutils.to_primitive(msg)
|
||||||
|
self.assertEqual(msg, ret)
|
||||||
|
|
||||||
|
def test_message_with_named_param(self):
|
||||||
|
msg = self.trans_fixture.lazy('A message with params: %(param)s')
|
||||||
|
msg = msg % {'param': 'hello'}
|
||||||
|
ret = jsonutils.to_primitive(msg)
|
||||||
|
self.assertEqual(msg, ret)
|
@ -23,6 +23,7 @@ classifier =
|
|||||||
packages =
|
packages =
|
||||||
oslo
|
oslo
|
||||||
oslo.serialization
|
oslo.serialization
|
||||||
|
oslo_serialization
|
||||||
namespace_packages =
|
namespace_packages =
|
||||||
oslo
|
oslo
|
||||||
|
|
||||||
|
61
tests/test_warning.py
Normal file
61
tests/test_warning.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslotest import base as test_base
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecationWarningTest(test_base.BaseTestCase):
|
||||||
|
|
||||||
|
@mock.patch('warnings.warn')
|
||||||
|
def test_warning(self, mock_warn):
|
||||||
|
import oslo.serialization
|
||||||
|
imp.reload(oslo.serialization)
|
||||||
|
self.assertTrue(mock_warn.called)
|
||||||
|
args = mock_warn.call_args
|
||||||
|
self.assertIn('oslo_serialization', args[0][0])
|
||||||
|
self.assertIn('deprecated', args[0][0])
|
||||||
|
self.assertTrue(issubclass(args[0][1], DeprecationWarning))
|
||||||
|
|
||||||
|
def test_real_warning(self):
|
||||||
|
with warnings.catch_warnings(record=True) as warning_msgs:
|
||||||
|
warnings.resetwarnings()
|
||||||
|
warnings.simplefilter('always', DeprecationWarning)
|
||||||
|
import oslo.serialization
|
||||||
|
|
||||||
|
# Use a separate function to get the stack level correct
|
||||||
|
# so we know the message points back to this file. This
|
||||||
|
# corresponds to an import or reload, which isn't working
|
||||||
|
# inside the test under Python 3.3. That may be due to a
|
||||||
|
# difference in the import implementation not triggering
|
||||||
|
# warnings properly when the module is reloaded, or
|
||||||
|
# because the warnings module is mostly implemented in C
|
||||||
|
# and something isn't cleanly resetting the global state
|
||||||
|
# used to track whether a warning needs to be
|
||||||
|
# emitted. Whatever the cause, we definitely see the
|
||||||
|
# warnings.warn() being invoked on a reload (see the test
|
||||||
|
# above) and warnings are reported on the console when we
|
||||||
|
# run the tests. A simpler test script run outside of
|
||||||
|
# testr does correctly report the warnings.
|
||||||
|
def foo():
|
||||||
|
oslo.serialization.deprecated()
|
||||||
|
|
||||||
|
foo()
|
||||||
|
self.assertEqual(1, len(warning_msgs))
|
||||||
|
msg = warning_msgs[0]
|
||||||
|
self.assertIn('oslo_serialization', six.text_type(msg.message))
|
||||||
|
self.assertEqual('test_warning.py', os.path.basename(msg.filename))
|
Loading…
x
Reference in New Issue
Block a user