From 7c64c8b95457c3aed1a3243ef1c22c303697a057 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 14 Aug 2008 18:50:21 +0000 Subject: 161: Registrar now takes advantage of DictProxy; updated corresponding unit tests --- ipalib/plugable.py | 78 ++++++++++++++++++++++++++++--------------- ipalib/tests/test_plugable.py | 46 ++++++++++++------------- 2 files changed, 74 insertions(+), 50 deletions(-) (limited to 'ipalib') diff --git a/ipalib/plugable.py b/ipalib/plugable.py index ffef8d643..b663a7ea4 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -102,6 +102,16 @@ class ReadOnly(object): 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): """ 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 assumed to). - One of these is created for each allowed base class in a `Registrar` - instance. + One of these is created for each allowed base in a `Registrar` instance. """ 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)) self.__d = d - self.__lock__() - assert self.__islocked__() + lock(self) def __len__(self): """ @@ -532,19 +540,44 @@ class DictProxy(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): """ :param allowed: Base classes from which plugins accepted by this 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.__registered = set() for base in self.__allowed: - assert inspect.isclass(base) - assert base.__name__ not in self.__d - self.__d[base.__name__] = {} - self.__lock__() + val = Val(base) + assert not ( + val.name in self.__d or hasattr(self, val.name) + ) + self.__d[val.name] = val + setattr(self, val.name, val.dictproxy) + lock(self) def __findbases(self, klass): """ @@ -580,7 +613,7 @@ class Registrar(ReadOnly): # Find the base class or raise SubclassError: for base in self.__findbases(klass): - sub_d = self.__d[base.__name__] + sub_d = self.__d[base.__name__].sub_d # Check override: if klass.__name__ in sub_d: @@ -598,26 +631,19 @@ class Registrar(ReadOnly): # The plugin is okay, add to __registered: self.__registered.add(klass) - def __getitem__(self, item): + def __getitem__(self, key): """ - Returns a copy of the namespace dict of the base class named - ``name``. + Returns the DictProxy for plugins subclassed from the base named ``key``. """ - if inspect.isclass(item): - if item not in self.__allowed: - raise KeyError(repr(item)) - key = item.__name__ - else: - key = item - return dict(self.__d[key]) + if key not in self.__d: + raise KeyError('no base class named %r' % key) + return self.__d[key].dictproxy - 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 item in self.__allowed - return item in self.__d + return key in self.__d def __iter__(self): """ @@ -625,7 +651,7 @@ class Registrar(ReadOnly): base. """ 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))) diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py index 2854ee6a6..b64cf305c 100644 --- a/ipalib/tests/test_plugable.py +++ b/ipalib/tests/test_plugable.py @@ -528,11 +528,10 @@ def test_Registrar(): # Test __hasitem__, __getitem__: for base in [Base1, Base2]: - assert base in r assert base.__name__ in r - assert r[base] == {} - assert r[base.__name__] == {} - + dp = 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 # a class: @@ -544,12 +543,12 @@ def test_Registrar(): # Check that registration works r(plugin1) - sub_d = r['Base1'] - assert len(sub_d) == 1 - assert sub_d['plugin1'] is plugin1 - # Check that a copy is returned - assert sub_d is not r['Base1'] - assert sub_d == r['Base1'] + dp = r['Base1'] + assert type(dp) is plugable.DictProxy + assert len(dp) == 1 + assert r.Base1 is dp + assert dp['plugin1'] is plugin1 + assert dp.plugin1 is plugin1 # Check that DuplicateError is raised trying to register exact class # again: @@ -566,21 +565,19 @@ def test_Registrar(): # Check that overriding works r(plugin1, override=True) - sub_d = r['Base1'] - assert len(sub_d) == 1 - assert sub_d['plugin1'] is plugin1 - assert sub_d['plugin1'] is not orig1 + assert len(r.Base1) == 1 + assert r.Base1.plugin1 is plugin1 + assert r.Base1.plugin1 is not orig1 # Check that MissingOverrideError is raised trying to override a name # not yet registerd: 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) - sub_d = r['Base2'] - assert len(sub_d) == 1 - assert sub_d['plugin2'] is plugin2 - + assert len(r.Base2) == 1 + assert r.Base2.plugin2 is plugin2 # Setup to test __iter__: class plugin1a(Base1): @@ -612,12 +609,13 @@ def test_Registrar(): # Again test __hasitem__, __getitem__: for base in [Base1, Base2]: - assert base in r assert base.__name__ in r - d = dict((p.__name__, p) for p in m[base.__name__]) - assert len(d) == 3 - assert r[base] == d - assert r[base.__name__] == d + dp = r[base.__name__] + assert len(dp) == 3 + for key in dp: + klass = dp[key] + assert getattr(dp, key) is klass + assert issubclass(klass, base) def test_API(): -- cgit