summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/cli.py4
-rw-r--r--ipalib/plugable.py145
-rw-r--r--ipalib/public.py4
-rw-r--r--ipalib/tests/test_cli.py6
-rw-r--r--ipalib/tests/test_plugable.py17
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