Support dynamic types

Some of the OpenStack projects need to be able to add
data to the input and output types used by the API,
in order to support extensions configured when the
service is deployed. This change adds a reregister()
method to the Registry that can be used to update the
registration of an existing class, and provides a
DynamicBase class with a convenience method for adding
new extension fields.

bp support-dynamic-types

Change-Id: Idf892de2129fddd7ddbb43847ebe0f14464a6e97
This commit is contained in:
Doug Hellmann 2013-11-19 17:36:47 -05:00
parent ec7d49f33c
commit cdf74daac2
2 changed files with 162 additions and 0 deletions

View File

@ -523,3 +523,107 @@ Value: 'v3'. Value should be one of: v., v.",
return 'from-file'
f = types.File(content=six.b('from-content'))
assert f.file.read() == six.b('from-content')
def test_unregister(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry._unregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, None)
def test_unregister_twice(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry._unregister(TempType)
# Second call should not raise an exception
types.registry._unregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, None)
def test_unregister_array_type(self):
class TempType(object):
pass
t = [TempType]
types.registry.register(t)
self.assertNotEqual(types.registry.array_types, set())
types.registry._unregister(t)
self.assertEqual(types.registry.array_types, set())
def test_unregister_array_type_twice(self):
class TempType(object):
pass
t = [TempType]
types.registry.register(t)
self.assertNotEqual(types.registry.array_types, set())
types.registry._unregister(t)
# Second call should not raise an exception
types.registry._unregister(t)
self.assertEqual(types.registry.array_types, set())
def test_unregister_dict_type(self):
class TempType(object):
pass
t = {str: TempType}
types.registry.register(t)
self.assertNotEqual(types.registry.dict_types, set())
types.registry._unregister(t)
self.assertEqual(types.registry.dict_types, set())
def test_unregister_dict_type_twice(self):
class TempType(object):
pass
t = {str: TempType}
types.registry.register(t)
self.assertNotEqual(types.registry.dict_types, set())
types.registry._unregister(t)
# Second call should not raise an exception
types.registry._unregister(t)
self.assertEqual(types.registry.dict_types, set())
def test_reregister(self):
class TempType(object):
pass
types.registry.register(TempType)
v = types.registry.lookup('TempType')
self.assertIs(v, TempType)
types.registry.reregister(TempType)
after = types.registry.lookup('TempType')
self.assertIs(after, TempType)
def test_reregister_and_add_attr(self):
class TempType(object):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.one = str
types.registry.reregister(TempType)
after = types.list_attributes(TempType)
self.assertNotEqual(after, [])
def test_dynamicbase_add_attributes(self):
class TempType(types.DynamicBase):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.add_attributes(one=str)
after = types.list_attributes(TempType)
self.assertEqual(len(after), 1)
def test_dynamicbase_add_attributes_second(self):
class TempType(types.DynamicBase):
pass
types.registry.register(TempType)
attrs = types.list_attributes(TempType)
self.assertEqual(attrs, [])
TempType.add_attributes(one=str)
TempType.add_attributes(two=int)
after = types.list_attributes(TempType)
self.assertEqual(len(after), 2)

View File

@ -670,6 +670,41 @@ class Registry(object):
self._complex_types.append(weakref.ref(class_))
return class_
def reregister(self, class_):
"""Register a type which may already have been registered.
"""
self._unregister(class_)
return self.register(class_)
def _unregister(self, class_):
"""Remove a previously registered type.
"""
# Clear the existing attribute reference so it is rebuilt if
# the class is registered again later.
if hasattr(class_, '_wsme_attributes'):
del class_._wsme_attributes
# FIXME(dhellmann): This method does not recurse through the
# types like register() does. Should it?
if isinstance(class_, list):
at = ArrayType(class_[0])
try:
self.array_types.remove(at)
except KeyError:
pass
elif isinstance(class_, dict):
key_type, value_type = list(class_.items())[0]
self.dict_types = set(
dt for dt in self.dict_types
if (dt.key_type, dt.value_type) != (key_type, value_type)
)
# We can't use remove() here because the items in
# _complex_types are weakref objects pointing to the classes,
# so we can't compare with them directly.
self._complex_types = [
ct for ct in self._complex_types
if ct() is not class_
]
def lookup(self, typename):
log.debug('Lookup %s' % typename)
modname = None
@ -772,3 +807,26 @@ class File(Base):
if self._file is None and self._content:
self._file = six.BytesIO(self._content)
return self._file
class DynamicBase(Base):
"""Base type for complex types for which all attributes are not
defined when the class is constructed.
This class is meant to be used as a base for types that have
properties added after the main class is created, such as by
loading plugins.
"""
@classmethod
def add_attributes(cls, **attrs):
"""Add more attributes
The arguments should be valid Python attribute names
associated with a type for the new attribute.
"""
for n, t in attrs.items():
setattr(cls, n, t)
cls.__registry__.reregister(cls)