From e8deb08b7ff13f246b70d31bd10f5db6b88b4244 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 5 Jan 2015 15:40:06 -0500 Subject: [PATCH] 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 --- doc/source/api.rst | 2 +- oslo/serialization/__init__.py | 26 ++ oslo/serialization/jsonutils.py | 224 +---------------- oslo_serialization/__init__.py | 0 oslo_serialization/jsonutils.py | 235 ++++++++++++++++++ oslo_serialization/tests/__init__.py | 0 oslo_serialization/tests/test_jsonutils.py | 275 +++++++++++++++++++++ setup.cfg | 1 + tests/test_warning.py | 61 +++++ 9 files changed, 600 insertions(+), 224 deletions(-) create mode 100644 oslo_serialization/__init__.py create mode 100644 oslo_serialization/jsonutils.py create mode 100644 oslo_serialization/tests/__init__.py create mode 100644 oslo_serialization/tests/test_jsonutils.py create mode 100644 tests/test_warning.py diff --git a/doc/source/api.rst b/doc/source/api.rst index da1af8e..b3596df 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -5,5 +5,5 @@ jsonutils ========= -.. automodule:: oslo.serialization.jsonutils +.. automodule:: oslo_serialization.jsonutils :members: diff --git a/oslo/serialization/__init__.py b/oslo/serialization/__init__.py index e69de29..73e54f3 100644 --- a/oslo/serialization/__init__.py +++ b/oslo/serialization/__init__.py @@ -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() diff --git a/oslo/serialization/jsonutils.py b/oslo/serialization/jsonutils.py index 7637d5a..74476f6 100644 --- a/oslo/serialization/jsonutils.py +++ b/oslo/serialization/jsonutils.py @@ -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 # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -15,221 +10,4 @@ # 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 - # 460353 - # 379632 - # 274610 - # 199918 - # 114200 - # 51817 - # 26164 - # 6491 - # 283 - # 19 - 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 `_ - :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 `_ - :param kwargs: extra named parameters, please see documentation \ - of `json.dump `_ - """ - 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 `_ - :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 `_ - :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__) +from oslo_serialization.jsonutils import * # noqa diff --git a/oslo_serialization/__init__.py b/oslo_serialization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oslo_serialization/jsonutils.py b/oslo_serialization/jsonutils.py new file mode 100644 index 0000000..7637d5a --- /dev/null +++ b/oslo_serialization/jsonutils.py @@ -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 + # 460353 + # 379632 + # 274610 + # 199918 + # 114200 + # 51817 + # 26164 + # 6491 + # 283 + # 19 + 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 `_ + :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 `_ + :param kwargs: extra named parameters, please see documentation \ + of `json.dump `_ + """ + 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 `_ + :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 `_ + :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__) diff --git a/oslo_serialization/tests/__init__.py b/oslo_serialization/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oslo_serialization/tests/test_jsonutils.py b/oslo_serialization/tests/test_jsonutils.py new file mode 100644 index 0000000..c7d6e5e --- /dev/null +++ b/oslo_serialization/tests/test_jsonutils.py @@ -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"") + else: + self.assertEqual(jsonutils.to_primitive(x), u"") + + 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".foo at 0x' + )) + else: + self.assertTrue(ret[1].startswith('') + + 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) diff --git a/setup.cfg b/setup.cfg index 1226940..092cc70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifier = packages = oslo oslo.serialization + oslo_serialization namespace_packages = oslo diff --git a/tests/test_warning.py b/tests/test_warning.py new file mode 100644 index 0000000..24100db --- /dev/null +++ b/tests/test_warning.py @@ -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))