Refactor ProviderAPIs object to better design pattern
Utilize a shared state design pattern (Borg) instead of singleton. This is a significantly better design pattern as it allows the ProviderAPIRegistry to be non-private and instantiated as needed but maintain exactly the same state across all the objects (data is stored on the class definition). A __setattr__ method has been implemented to avoid accidental changes or data creep to end up on the Registry itself. Change-Id: Ic7c84af014bb20158d7c23340da9d8cdae3908c8
This commit is contained in:
parent
94f8f103ab
commit
0a641462cb
@ -10,60 +10,67 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
ProviderAPIs = None
|
|
||||||
|
|
||||||
|
class ProviderAPIRegistry(object):
|
||||||
|
__shared_object_state = {}
|
||||||
|
__registry = {}
|
||||||
|
__iter__ = __registry.__iter__
|
||||||
|
__getitem__ = __registry.__getitem__
|
||||||
|
locked = False
|
||||||
|
|
||||||
def _create_provider_api_instance():
|
def __init__(self):
|
||||||
class _ProviderAPIs(object):
|
# NOTE(morgan): This rebinds __dict__ and allows all instances of
|
||||||
|
# the provider API to share a common state. Any changes except
|
||||||
|
# rebinding __dict__ will maintain the same state stored on the class
|
||||||
|
# not the instance. This design pattern is preferable to
|
||||||
|
# full singletons where state sharing is the important "feature"
|
||||||
|
# derived from the "singleton"
|
||||||
|
#
|
||||||
|
# Use "super" to bypass the __setattr__ preventing changes to the
|
||||||
|
# object itself.
|
||||||
|
super(ProviderAPIRegistry, self).__setattr__(
|
||||||
|
'__dict__', self.__shared_object_state)
|
||||||
|
|
||||||
def __init__(self):
|
def __getattr__(self, item):
|
||||||
self.__registry = {}
|
"""Do attr lookup."""
|
||||||
self.__locked = False
|
try:
|
||||||
|
return self.__registry[item]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
"'ProviderAPIs' has no attribute %s" % item)
|
||||||
|
|
||||||
self.__iter__ = self.__registry.__iter__
|
def __setattr__(self, key, value):
|
||||||
self.__getitem__ = self.__registry.__getitem__
|
"""Do not allow setting values on the registry object."""
|
||||||
|
raise RuntimeError('Programming Error: You may not set values on the '
|
||||||
|
'ProviderAPIRegistry objects.')
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def _register_provider_api(self, name, obj):
|
||||||
"""Do attr lookup."""
|
"""Register an instance of a class as a provider api."""
|
||||||
try:
|
if name == 'driver':
|
||||||
return self.__registry[item]
|
raise ValueError('A provider may not be named "driver".')
|
||||||
except KeyError:
|
|
||||||
raise AttributeError(
|
|
||||||
"'ProviderAPIs' has no attribute %s" % item)
|
|
||||||
|
|
||||||
def _register_provider_api(self, name, obj):
|
if self.locked:
|
||||||
"""Register an instance of a class as a provider api."""
|
raise RuntimeError(
|
||||||
if name == 'driver':
|
'Programming Error: The provider api registry has been '
|
||||||
raise ValueError('A provider may not be named "driver".')
|
'locked (post configuration). Ensure all provider api '
|
||||||
|
'managers are instantiated before locking.')
|
||||||
|
|
||||||
if self.__locked:
|
if name in self.__registry:
|
||||||
raise RuntimeError(
|
raise DuplicateProviderError(
|
||||||
'Programming Error: The provider api registry has been '
|
'`%(name)s` has already been registered as an api '
|
||||||
'locked (post configuration). Ensure all provider api '
|
'provider by `%(prov)r`' % {'name': name,
|
||||||
'managers are instantiated before locking.')
|
'prov': self.__registry[name]})
|
||||||
|
self.__registry[name] = obj
|
||||||
|
|
||||||
if name in self.__registry:
|
def _clear_registry_instances(self):
|
||||||
raise DuplicateProviderError(
|
"""ONLY USED FOR TESTING."""
|
||||||
'`%(name)s` has already been registered as an api '
|
self.__registry.clear()
|
||||||
'provider by `%(prov)r`' % {'name': name,
|
# Use super to allow setting around class implementation of __setattr__
|
||||||
'prov': self.__registry[name]})
|
super(ProviderAPIRegistry, self).__setattr__('locked', False)
|
||||||
self.__registry[name] = obj
|
|
||||||
|
|
||||||
def _clear_registry_instances(self):
|
def lock_provider_registry(self):
|
||||||
"""ONLY USED FOR TESTING."""
|
# Use super to allow setting around class implementation of __setattr__
|
||||||
self.__registry.clear()
|
super(ProviderAPIRegistry, self).__setattr__('locked', True)
|
||||||
self.__locked = False
|
|
||||||
|
|
||||||
def lock_provider_registry(self):
|
|
||||||
self.__locked = True
|
|
||||||
|
|
||||||
global ProviderAPIs
|
|
||||||
if ProviderAPIs is None:
|
|
||||||
ProviderAPIs = _ProviderAPIs()
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Programming Error: ProviderAPIs object cannot be '
|
|
||||||
'instatiated more than one time. It is meant to '
|
|
||||||
'act as a singleton.')
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateProviderError(Exception):
|
class DuplicateProviderError(Exception):
|
||||||
@ -84,4 +91,4 @@ class ProviderAPIMixin(object):
|
|||||||
return self.__getattribute__(item)
|
return self.__getattribute__(item)
|
||||||
|
|
||||||
|
|
||||||
_create_provider_api_instance()
|
ProviderAPIs = ProviderAPIRegistry()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user