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:
parent
ec7d49f33c
commit
cdf74daac2
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user