diff options
-rw-r--r-- | ipalib/cli.py | 4 | ||||
-rw-r--r-- | ipalib/plugable.py | 145 | ||||
-rw-r--r-- | ipalib/public.py | 4 | ||||
-rw-r--r-- | ipalib/tests/test_cli.py | 6 | ||||
-rw-r--r-- | ipalib/tests/test_plugable.py | 17 |
5 files changed, 122 insertions, 54 deletions
diff --git a/ipalib/cli.py b/ipalib/cli.py index d17d12bca..8f09e90ce 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -65,7 +65,7 @@ class CLI(object): def print_commands(self): print 'Available Commands:' - for cmd in self.api.cmd: + for cmd in self.api.cmd(): print ' %s %s' % ( to_cli(cmd.name).ljust(self.mcl), cmd.doc, @@ -84,7 +84,7 @@ class CLI(object): api.register(help) api.finalize() def d_iter(): - for cmd in api.cmd: + for cmd in api.cmd(): yield (to_cli(cmd.name), cmd) self.__d = dict(d_iter()) diff --git a/ipalib/plugable.py b/ipalib/plugable.py index fbe5e638f..ec97722a8 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -26,16 +26,6 @@ import inspect import errors -def check_identifier(name): - """ - Raises errors.NameSpaceError if `name` is not a valid Python identifier - suitable for use in a NameSpace. - """ - regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' - if re.match(regex, name) is None: - raise errors.NameSpaceError(name, regex) - - class ReadOnly(object): """ Base class for classes with read-only attributes. @@ -151,7 +141,6 @@ class Proxy(ReadOnly): self.doc = target.doc self.__lock__() assert type(self.__public__) is frozenset - check_identifier(self.name) def implements(self, arg): """ @@ -341,60 +330,132 @@ class Plugin(ProxyTarget): ) -class NameSpace(ReadOnly): +def check_name(name): """ - A read-only namespace of Proxy instances. Proxy.name is used to name the - attributes pointing to the Proxy instances, which can also be accesses - through a dictionary interface, for example: + Raises errors.NameSpaceError if `name` is not a valid Python identifier + suitable for use in a NameSpace. + """ + assert type(name) is str, 'must be %r' % str + regex = r'^[a-z][_a-z0-9]*[a-z0-9]$' + if re.match(regex, name) is None: + raise errors.NameSpaceError(name, regex) + return name - >>> assert namespace.my_proxy is namespace['my_proxy'] # True + +class NameSpace(ReadOnly): + """ + A read-only namespace with handy container behaviours. + + Each member of a NameSpace instance must have a `name` attribute whose + value 1) is unique among the members and 2) passes the check_name() + function. Beyond that, no restrictions are placed on the members: they can + be classes or instances, and of any type. + + The members can be accessed as attributes on the NameSpace instance or + through a dictionary interface. For example, assuming `obj` is a member in + the NameSpace instance `namespace`: + + >>> obj is getattr(namespace, obj.name) # As attribute + True + >>> obj is namespace[obj.name] # As dictionary item + True + + Here is a more detailed example: + + >>> class member(object): + ... def __init__(self, i): + ... self.name = 'member_%d' % i + ... + >>> def get_members(cnt): + ... for i in xrange(cnt): + ... yield member(i) + ... + >>> namespace = NameSpace(get_members(2)) + >>> namespace.member_0 is namespace['member_0'] + True + >>> len(namespace) # Returns the number of members in namespace + 2 + >>> list(namespace) # As iterable, iterates through the member names + ['member_0', 'member_1'] + >>> list(namespace()) # Calling a NameSpace iterates through the members + [<__main__.member object at 0x836710>, <__main__.member object at 0x836750>] + >>> 'member_1' in namespace # NameSpace.__contains__() + True """ - def __init__(self, proxies): + def __init__(self, members): """ - `proxies` - an iterable returning the Proxy instances to be contained - in this NameSpace. + @type members: iterable + @param members: An iterable providing the members. """ - self.__proxies = tuple(proxies) self.__d = dict() - for proxy in self.__proxies: - assert isinstance(proxy, Proxy) - assert proxy.name not in self.__d - self.__d[proxy.name] = proxy - assert not hasattr(self, proxy.name) - setattr(self, proxy.name, proxy) + self.__names = tuple(self.__member_iter(members)) self.__lock__() + assert set(self.__d) == set(self.__names) - def __iter__(self): + def __member_iter(self, members): """ - Iterates through the proxies in this NameSpace in the same order they - were passed to the constructor. + Helper method used only from __init__(). """ - for proxy in self.__proxies: - yield proxy + for member in members: + name = check_name(member.name) + assert not ( + name in self.__d or hasattr(self, name) + ), 'already has member named %r' % name + self.__d[name] = member + setattr(self, name, member) + yield name def __len__(self): """ - Returns number of proxies in this NameSpace. + Returns the number of members in this NameSpace. """ - return len(self.__proxies) + return len(self.__d) - def __contains__(self, key): + def __contains__(self, name): """ - Returns True if a proxy named `key` is in this NameSpace. + Returns True if this NameSpace contains a member named `name`; returns + False otherwise. + + @type name: str + @param name: The name of a potential member """ - return key in self.__d + return name in self.__d - def __getitem__(self, key): + def __getitem__(self, name): + """ + If this NameSpace contains a member named `name`, returns that member; + otherwise raises KeyError. + + @type name: str + @param name: The name of member to retrieve + """ + if name in self.__d: + return self.__d[name] + raise KeyError('NameSpace has no member named %r' % name) + + def __iter__(self): """ - Returns proxy named `key`; otherwise raises KeyError. + Iterates through the member names in the same order as the members + were passed to the constructor. """ - if key in self.__d: - return self.__d[key] - raise KeyError('NameSpace has no item for key %r' % key) + for name in self.__names: + yield name + + def __call__(self): + """ + Iterates through the members in the same order they were passed to the + constructor. + """ + for name in self.__names: + yield self.__d[name] def __repr__(self): - return '%s(<%d proxies>)' % (self.__class__.__name__, len(self)) + """ + Returns pseudo-valid Python expression that could be used to construct + this NameSpace instance. + """ + return '%s(<%d members>)' % (self.__class__.__name__, len(self)) class Registrar(ReadOnly): diff --git a/ipalib/public.py b/ipalib/public.py index 15f08b5b4..d23aa7acc 100644 --- a/ipalib/public.py +++ b/ipalib/public.py @@ -187,7 +187,7 @@ class cmd(plugable.Plugin): return dict(self.normalize_iter(kw)) def default_iter(self, kw): - for option in self.options: + for option in self.options(): if option.name not in kw: value = option.default(**kw) if value is not None: @@ -199,7 +199,7 @@ class cmd(plugable.Plugin): def validate(self, **kw): self.print_call('validate', kw, 1) - for opt in self.options: + for opt in self.options(): value = kw.get(opt.name, None) if value is None: if opt.required: diff --git a/ipalib/tests/test_cli.py b/ipalib/tests/test_cli.py index ad02c6459..4c14c0dd9 100644 --- a/ipalib/tests/test_cli.py +++ b/ipalib/tests/test_cli.py @@ -22,7 +22,7 @@ Unit tests for `ipalib.cli` module. """ from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker -from ipalib import cli +from ipalib import cli, plugable def test_to_cli(): @@ -50,7 +50,7 @@ class DummyCmd(object): class DummyAPI(object): def __init__(self, cnt): - self.__cmd = tuple(self.__cmd_iter(cnt)) + self.__cmd = plugable.NameSpace(self.__cmd_iter(cnt)) def __get_cmd(self): return self.__cmd @@ -123,7 +123,7 @@ class test_CLI(ClassChecker): assert len(api.cmd) == cnt o = self.cls(api) o.finalize() - for cmd in api.cmd: + for cmd in api.cmd(): key = cli.to_cli(cmd.name) assert key in o assert o[key] is cmd diff --git a/ipalib/tests/test_plugable.py b/ipalib/tests/test_plugable.py index ba90c2034..bf1ef91cd 100644 --- a/ipalib/tests/test_plugable.py +++ b/ipalib/tests/test_plugable.py @@ -26,11 +26,11 @@ from tstutil import ClassChecker from ipalib import plugable, errors -def test_valid_identifier(): +def test_check_name(): """ - Test the `valid_identifier` function. + Tests the `check_name` function. """ - f = plugable.check_identifier + f = plugable.check_name okay = [ 'user_add', 'stuff2junk', @@ -426,13 +426,20 @@ class test_NameSpace(ClassChecker): # Test __iter__ i = None - for (i, proxy) in enumerate(ns): + for (i, key) in enumerate(ns): + assert type(key) is str + assert key == get_name(i) + assert i == cnt - 1 + + # Test __call__ + i = None + for (i, proxy) in enumerate(ns()): assert type(proxy) is plugable.Proxy assert proxy.name == get_name(i) assert i == cnt - 1 # Test __contains__, __getitem__, getattr(): - proxies = frozenset(ns) + proxies = frozenset(ns()) for i in xrange(cnt): name = get_name(i) assert name in ns |