diff --git a/oslo_versionedobjects/base.py b/oslo_versionedobjects/base.py index 9d6e8bc3..f495c9db 100644 --- a/oslo_versionedobjects/base.py +++ b/oslo_versionedobjects/base.py @@ -59,7 +59,7 @@ def make_class_properties(cls): for name, field in supercls.fields.items(): if name not in cls.fields: cls.fields[name] = field - for name, field in cls.fields.iteritems(): + for name, field in six.iteritems(cls.fields): if not isinstance(field, fields.Field): raise exception.ObjectFieldInvalid( field=name, objname=cls.obj_name()) @@ -558,7 +558,7 @@ class VersionedObject(object): @property def obj_fields(self): - return self.fields.keys() + self.obj_extra_fields + return list(self.fields.keys()) + self.obj_extra_fields class VersionedObjectDictCompat(object): @@ -721,8 +721,8 @@ class ObjectListBase(object): """List index of value.""" return self.objects.index(value) - def sort(self, cmp=None, key=None, reverse=False): - self.objects.sort(cmp=cmp, key=key, reverse=reverse) + def sort(self, key=None, reverse=False): + self.objects.sort(key=key, reverse=reverse) def obj_make_compatible(self, primitive, target_version): primitives = primitive['objects'] diff --git a/oslo_versionedobjects/exception.py b/oslo_versionedobjects/exception.py index 847f60c8..c844e891 100644 --- a/oslo_versionedobjects/exception.py +++ b/oslo_versionedobjects/exception.py @@ -28,6 +28,7 @@ import sys from oslo_config import cfg from oslo_utils import excutils +import six import webob.exc from oslo_versionedobjects._i18n import _, _LE @@ -126,7 +127,7 @@ class VersionedObjectsException(Exception): LOG.error("%s: %s" % (name, value)) # noqa if CONF.fatal_exception_format_errors: - raise exc_info[0], exc_info[1], exc_info[2] + raise six.reraise(*exc_info) else: # at least get the core message out if something happened message = self.msg_fmt diff --git a/oslo_versionedobjects/fields.py b/oslo_versionedobjects/fields.py index e0b331c4..ce891044 100755 --- a/oslo_versionedobjects/fields.py +++ b/oslo_versionedobjects/fields.py @@ -132,13 +132,8 @@ class Field(object): self._read_only = read_only def __repr__(self): - args = { - 'nullable': self._nullable, - 'default': self._default, - } - return '%s(%s)' % (self._type.__class__.__name__, - ','.join(['%s=%s' % (k, v) - for k, v in args.items()])) + return '%s(default=%s,nullable=%s)' % (self._type.__class__.__name__, + self._default, self._nullable) @property def nullable(self): @@ -238,9 +233,10 @@ class String(FieldType): @staticmethod def coerce(obj, attr, value): # FIXME(danms): We should really try to avoid the need to do this - if isinstance(value, (six.string_types, int, long, float, - datetime.datetime)): - return unicode(value) + accepted_types = six.integer_types + (float, six.string_types, + datetime.datetime) + if isinstance(value, accepted_types): + return six.text_type(value) else: raise ValueError(_('A string is required here, not %s') % value.__class__.__name__) diff --git a/oslo_versionedobjects/tests/test_fields.py b/oslo_versionedobjects/tests/test_fields.py index c208f9d0..21e7d1b2 100755 --- a/oslo_versionedobjects/tests/test_fields.py +++ b/oslo_versionedobjects/tests/test_fields.py @@ -16,6 +16,7 @@ import datetime import iso8601 from oslo_utils import timeutils +import six from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import fields @@ -72,8 +73,10 @@ class TestString(TestField): def setUp(self): super(TestField, self).setUp() self.field = fields.StringField() - self.coerce_good_values = [('foo', 'foo'), (1, '1'), (1L, '1'), - (True, 'True')] + self.coerce_good_values = [ + ('foo', 'foo'), (1, '1'), (1.0, '1.0'), (True, 'True')] + if six.PY2: + self.coerce_good_values += [(long(1), '1')] self.coerce_bad_values = [None] self.to_primitive_values = self.coerce_good_values[0:1] self.from_primitive_values = self.coerce_good_values[0:1] diff --git a/oslo_versionedobjects/tests/test_objects.py b/oslo_versionedobjects/tests/test_objects.py index b71b28fc..f24d6501 100755 --- a/oslo_versionedobjects/tests/test_objects.py +++ b/oslo_versionedobjects/tests/test_objects.py @@ -20,6 +20,7 @@ import inspect import logging import os import pprint +import six import mock from oslo_context import context @@ -569,14 +570,13 @@ class _TestObject(object): 'foo', '1.0') def test_obj_class_from_name_supported_version(self): - error = None + self.assertRaises(exception.IncompatibleObjectVersion, + base.VersionedObject.obj_class_from_name, + 'MyObj', '1.25') try: base.VersionedObject.obj_class_from_name('MyObj', '1.25') except exception.IncompatibleObjectVersion as error: - pass - - self.assertIsNotNone(error) - self.assertEqual('1.6', error.kwargs['supported']) + self.assertEqual('1.6', error.kwargs['supported']) def test_orphaned_object(self): obj = MyObj.query(self.context) @@ -665,7 +665,7 @@ class _TestObject(object): 'versioned_object.namespace': 'versionedobjects', 'versioned_object.version': '1.6', 'versioned_object.changes': [ - 'deleted', 'created_at', 'deleted_at', 'updated_at', + 'created_at', 'deleted', 'deleted_at', 'updated_at', ], 'versioned_object.data': { 'created_at': timeutils.isotime(dt), @@ -674,7 +674,10 @@ class _TestObject(object): 'deleted': False, }, } - self.assertEqual(obj.obj_to_primitive(), expected) + + got = obj.obj_to_primitive() + got['versioned_object.changes'].sort() + self.assertEqual(got, expected) def test_contains(self): obj = MyObj() @@ -707,7 +710,7 @@ class _TestObject(object): self.assertRaises(AttributeError, obj.get, 'nothing', 3) def test_object_inheritance(self): - base_fields = base.VersionedPersistentObject.fields.keys() + base_fields = list(base.VersionedPersistentObject.fields.keys()) myobj_fields = (['foo', 'bar', 'missing', 'readonly', 'rel_object', 'rel_objects'] + base_fields) @@ -1119,11 +1122,11 @@ class TestObjectSerializer(_BaseTestCase): thing = {'key': obj} primitive = ser.serialize_entity(self.context, thing) self.assertEqual(1, len(primitive)) - for item in primitive.itervalues(): + for item in six.itervalues(primitive): self.assertNotIsInstance(item, base.VersionedObject) thing2 = ser.deserialize_entity(self.context, primitive) self.assertEqual(1, len(thing2)) - for item in thing2.itervalues(): + for item in six.itervalues(thing2): self.assertIsInstance(item, MyObj) # object-action updates dict case @@ -1172,7 +1175,8 @@ class TestObjectVersions(test.TestCase): """Follow a chain of remotable things down to the original function.""" if isinstance(thing, classmethod): return self._find_remotable_method(cls, thing.__get__(None, cls)) - elif inspect.ismethod(thing) and hasattr(thing, 'remotable'): + elif (inspect.ismethod(thing) + or inspect.isfunction(thing)) and hasattr(thing, 'remotable'): return self._find_remotable_method(cls, thing.original_fn, parent_was_remotable=True) elif parent_was_remotable: @@ -1185,12 +1189,13 @@ class TestObjectVersions(test.TestCase): def _get_fingerprint(self, obj_name): obj_class = base.VersionedObjectRegistry.obj_classes()[obj_name][0] - fields = obj_class.fields.items() + fields = list(obj_class.fields.items()) fields.sort() methods = [] for name in dir(obj_class): thing = getattr(obj_class, name) - if inspect.ismethod(thing) or isinstance(thing, classmethod): + if inspect.ismethod(thing) or inspect.isfunction(thing) \ + or isinstance(thing, classmethod): method = self._find_remotable_method(obj_class, thing) if method: methods.append((name, inspect.getargspec(method))) @@ -1204,13 +1209,13 @@ class TestObjectVersions(test.TestCase): relevant_data = (fields, methods, obj_class.child_versions) else: relevant_data = (fields, methods) - fingerprint = '%s-%s' % (obj_class.VERSION, - hashlib.md5(str(relevant_data)).hexdigest()) + fingerprint = '%s-%s' % (obj_class.VERSION, hashlib.md5( + six.binary_type(repr(relevant_data).encode())).hexdigest()) return fingerprint def test_versions(self): fingerprints = {} - for obj_name in base.VersionedObjectRegistry.obj_classes(): + for obj_name in sorted(base.VersionedObjectRegistry.obj_classes()): obj_classes = base.VersionedObjectRegistry._registry._obj_classes obj_cls = obj_classes[obj_name][0] if is_test_object(obj_cls): @@ -1218,8 +1223,8 @@ class TestObjectVersions(test.TestCase): fingerprints[obj_name] = self._get_fingerprint(obj_name) if os.getenv('GENERATE_HASHES'): - file('object_hashes.txt', 'w').write( - pprint.pformat(fingerprints)) + with open('object_hashes.txt', 'w') as fp: + fp.write(pprint.pformat(fingerprints)) raise test.TestingException( 'Generated hashes in object_hashes.txt') diff --git a/oslo_versionedobjects/utils.py b/oslo_versionedobjects/utils.py index 601e24a6..04928bad 100644 --- a/oslo_versionedobjects/utils.py +++ b/oslo_versionedobjects/utils.py @@ -17,6 +17,7 @@ """Utilities and helper functions.""" +import functools import logging import six @@ -32,7 +33,7 @@ def convert_version_to_int(version): if isinstance(version, six.string_types): version = convert_version_to_tuple(version) if isinstance(version, tuple): - return reduce(lambda x, y: (x * 1000) + y, version) + return functools.reduce(lambda x, y: (x * 1000) + y, version) except Exception: msg = _("Hypervisor version %s is invalid.") % version raise exception.VersionedObjectsException(msg) @@ -44,9 +45,9 @@ def convert_version_to_str(version_int): while version_int != 0: version_number = version_int - (version_int // factor * factor) version_numbers.insert(0, str(version_number)) - version_int = version_int / factor + version_int = version_int // factor - return reduce(lambda x, y: "%s.%s" % (x, y), version_numbers) + return '.'.join(map(str, version_numbers)) def convert_version_to_tuple(version_str): diff --git a/requirements-py3.txt b/requirements-py3.txt new file mode 100644 index 00000000..b6617a5c --- /dev/null +++ b/requirements-py3.txt @@ -0,0 +1,12 @@ +eventlet>=0.16.1 +six>=1.7.0 +Babel>=1.3 +netaddr>=0.7.12 +oslo.concurrency>=1.4.1 # Apache-2.0 +oslo.context>=0.1.0 # Apache-2.0 +oslo.messaging>=1.4.0,!=1.5.0 +oslo.serialization>=1.2.0 # Apache-2.0 +oslo.utils>=1.2.0 # Apache-2.0 +iso8601>=0.1.9 +oslo.log>=0.1.0 +oslo.i18n>=1.3.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 28005d33..3c48658b 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,6 @@ install_command = pip install -U {opts} {packages} # fails with a random hash seed, so set PYTHONHASHSEED. setenv = VIRTUAL_ENV={envdir} - PYTHONHASHSEED=0 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' @@ -22,6 +21,10 @@ commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 +[testenv:py34] +deps = -r{toxinidir}/requirements-py3.txt + -r{toxinidir}/test-requirements.txt + [testenv:venv] commands = {posargs}