161: Registrar now takes advantage of DictProxy; updated corresponding unit tests

This commit is contained in:
Jason Gerard DeRose
2008-08-14 18:50:21 +00:00
parent 87cad5078a
commit 7c64c8b954
2 changed files with 74 additions and 50 deletions

View File

@@ -102,6 +102,16 @@ class ReadOnly(object):
return object.__delattr__(self, name) return object.__delattr__(self, name)
def lock(obj):
"""
Convenience function to lock a `ReadOnly` instance.
"""
assert isinstance(obj, ReadOnly)
obj.__lock__()
assert obj.__islocked__()
return obj
class Plugin(ReadOnly): class Plugin(ReadOnly):
""" """
Base class for all plugins. Base class for all plugins.
@@ -477,8 +487,7 @@ class DictProxy(ReadOnly):
Although a DictProxy is read-only, the underlying dict can change (and is Although a DictProxy is read-only, the underlying dict can change (and is
assumed to). assumed to).
One of these is created for each allowed base class in a `Registrar` One of these is created for each allowed base in a `Registrar` instance.
instance.
""" """
def __init__(self, d): def __init__(self, d):
""" """
@@ -486,8 +495,7 @@ class DictProxy(ReadOnly):
""" """
assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d)) assert type(d) is dict, '`d` must be %r, got %r' % (dict, type(d))
self.__d = d self.__d = d
self.__lock__() lock(self)
assert self.__islocked__()
def __len__(self): def __len__(self):
""" """
@@ -532,19 +540,44 @@ class DictProxy(ReadOnly):
class Registrar(ReadOnly): class Registrar(ReadOnly):
"""
Collects plugin classes as they are registered.
The Registrar does not instantiate plugins... it only implements the
override logic and stores the plugins in a namespace per allowed base
class.
The plugins are instantiated when `API.finalize()` is called.
"""
def __init__(self, *allowed): def __init__(self, *allowed):
""" """
:param allowed: Base classes from which plugins accepted by this :param allowed: Base classes from which plugins accepted by this
Registrar must subclass. Registrar must subclass.
""" """
self.__allowed = frozenset(allowed)
class Val(ReadOnly):
"""
Internal class used so that only one mapping is needed.
"""
def __init__(self, base):
assert inspect.isclass(base)
self.base = base
self.name = base.__name__
self.sub_d = dict()
self.dictproxy = DictProxy(self.sub_d)
lock(self)
self.__allowed = allowed
self.__d = {} self.__d = {}
self.__registered = set() self.__registered = set()
for base in self.__allowed: for base in self.__allowed:
assert inspect.isclass(base) val = Val(base)
assert base.__name__ not in self.__d assert not (
self.__d[base.__name__] = {} val.name in self.__d or hasattr(self, val.name)
self.__lock__() )
self.__d[val.name] = val
setattr(self, val.name, val.dictproxy)
lock(self)
def __findbases(self, klass): def __findbases(self, klass):
""" """
@@ -580,7 +613,7 @@ class Registrar(ReadOnly):
# Find the base class or raise SubclassError: # Find the base class or raise SubclassError:
for base in self.__findbases(klass): for base in self.__findbases(klass):
sub_d = self.__d[base.__name__] sub_d = self.__d[base.__name__].sub_d
# Check override: # Check override:
if klass.__name__ in sub_d: if klass.__name__ in sub_d:
@@ -598,26 +631,19 @@ class Registrar(ReadOnly):
# The plugin is okay, add to __registered: # The plugin is okay, add to __registered:
self.__registered.add(klass) self.__registered.add(klass)
def __getitem__(self, item): def __getitem__(self, key):
""" """
Returns a copy of the namespace dict of the base class named Returns the DictProxy for plugins subclassed from the base named ``key``.
``name``.
""" """
if inspect.isclass(item): if key not in self.__d:
if item not in self.__allowed: raise KeyError('no base class named %r' % key)
raise KeyError(repr(item)) return self.__d[key].dictproxy
key = item.__name__
else:
key = item
return dict(self.__d[key])
def __contains__(self, item): def __contains__(self, key):
""" """
Returns True if a base class named ``name`` is in this Registrar. Returns True if a base class named ``key`` is in this Registrar.
""" """
if inspect.isclass(item): return key in self.__d
return item in self.__allowed
return item in self.__d
def __iter__(self): def __iter__(self):
""" """
@@ -625,7 +651,7 @@ class Registrar(ReadOnly):
base. base.
""" """
for base in self.__allowed: for base in self.__allowed:
sub_d = self.__d[base.__name__] sub_d = self.__d[base.__name__].sub_d
yield (base, tuple(sub_d[k] for k in sorted(sub_d))) yield (base, tuple(sub_d[k] for k in sorted(sub_d)))

View File

@@ -528,11 +528,10 @@ def test_Registrar():
# Test __hasitem__, __getitem__: # Test __hasitem__, __getitem__:
for base in [Base1, Base2]: for base in [Base1, Base2]:
assert base in r
assert base.__name__ in r assert base.__name__ in r
assert r[base] == {} dp = r[base.__name__]
assert r[base.__name__] == {} assert type(dp) is plugable.DictProxy
assert len(dp) == 0
# Check that TypeError is raised trying to register something that isn't # Check that TypeError is raised trying to register something that isn't
# a class: # a class:
@@ -544,12 +543,12 @@ def test_Registrar():
# Check that registration works # Check that registration works
r(plugin1) r(plugin1)
sub_d = r['Base1'] dp = r['Base1']
assert len(sub_d) == 1 assert type(dp) is plugable.DictProxy
assert sub_d['plugin1'] is plugin1 assert len(dp) == 1
# Check that a copy is returned assert r.Base1 is dp
assert sub_d is not r['Base1'] assert dp['plugin1'] is plugin1
assert sub_d == r['Base1'] assert dp.plugin1 is plugin1
# Check that DuplicateError is raised trying to register exact class # Check that DuplicateError is raised trying to register exact class
# again: # again:
@@ -566,21 +565,19 @@ def test_Registrar():
# Check that overriding works # Check that overriding works
r(plugin1, override=True) r(plugin1, override=True)
sub_d = r['Base1'] assert len(r.Base1) == 1
assert len(sub_d) == 1 assert r.Base1.plugin1 is plugin1
assert sub_d['plugin1'] is plugin1 assert r.Base1.plugin1 is not orig1
assert sub_d['plugin1'] is not orig1
# Check that MissingOverrideError is raised trying to override a name # Check that MissingOverrideError is raised trying to override a name
# not yet registerd: # not yet registerd:
raises(errors.MissingOverrideError, r, plugin2, override=True) raises(errors.MissingOverrideError, r, plugin2, override=True)
# Check that additional plugin can be registered: # Test that another plugin can be registered:
assert len(r.Base2) == 0
r(plugin2) r(plugin2)
sub_d = r['Base2'] assert len(r.Base2) == 1
assert len(sub_d) == 1 assert r.Base2.plugin2 is plugin2
assert sub_d['plugin2'] is plugin2
# Setup to test __iter__: # Setup to test __iter__:
class plugin1a(Base1): class plugin1a(Base1):
@@ -612,12 +609,13 @@ def test_Registrar():
# Again test __hasitem__, __getitem__: # Again test __hasitem__, __getitem__:
for base in [Base1, Base2]: for base in [Base1, Base2]:
assert base in r
assert base.__name__ in r assert base.__name__ in r
d = dict((p.__name__, p) for p in m[base.__name__]) dp = r[base.__name__]
assert len(d) == 3 assert len(dp) == 3
assert r[base] == d for key in dp:
assert r[base.__name__] == d klass = dp[key]
assert getattr(dp, key) is klass
assert issubclass(klass, base)
def test_API(): def test_API():