summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/cli.py2
-rw-r--r--ipalib/frontend.py10
-rw-r--r--ipalib/plugable.py74
-rw-r--r--ipatests/test_ipalib/test_cli.py2
-rw-r--r--ipatests/test_ipalib/test_frontend.py20
5 files changed, 74 insertions, 34 deletions
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 0de268249..374429f46 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -967,7 +967,7 @@ class show_api(frontend.Command):
continue
for n in member:
attr = member[n]
- if isinstance(attr, plugable.NameSpace) and len(attr) > 0:
+ if isinstance(attr, plugable.APINameSpace) and len(attr) > 0:
self.__traverse_namespace(n, attr, lines, tab + 2)
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index ffcf71b5a..161d7c391 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -28,7 +28,7 @@ import six
from ipapython.version import API_VERSION
from ipapython.ipa_log_manager import root_logger
from ipalib.base import NameSpace
-from ipalib.plugable import Plugin
+from ipalib.plugable import Plugin, APINameSpace
from ipalib.parameters import create_param, Param, Str, Flag
from ipalib.parameters import Password # pylint: disable=unused-import
from ipalib.output import Output, Entry, ListOfEntries
@@ -402,8 +402,6 @@ class Command(HasParam):
allowed callback types.
"""
- finalize_early = False
-
takes_options = tuple()
takes_args = tuple()
# Create stubs for attributes that are set in _on_finalize()
@@ -1199,8 +1197,6 @@ class Local(Command):
class Object(HasParam):
- finalize_early = False
-
# Create stubs for attributes that are set in _on_finalize()
backend = Plugin.finalize_attr('backend')
methods = Plugin.finalize_attr('methods')
@@ -1261,7 +1257,7 @@ class Object(HasParam):
if name not in self.api:
return
namespace = self.api[name]
- assert type(namespace) is NameSpace
+ assert type(namespace) is APINameSpace
for plugin in namespace(): # Equivalent to dict.itervalues()
if plugin.obj_name == self.name:
yield plugin
@@ -1333,8 +1329,6 @@ class Attribute(Plugin):
In practice the `Attribute` class is not used directly, but rather is
only the base class for the `Method` class. Also see the `Object` class.
"""
- finalize_early = False
-
@property
def obj_name(self):
return self.name.partition('_')[0]
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
index d8ff5c186..8284cca39 100644
--- a/ipalib/plugable.py
+++ b/ipalib/plugable.py
@@ -40,7 +40,7 @@ from ipalib import errors
from ipalib.config import Env
from ipalib.text import _
from ipalib.util import classproperty
-from ipalib.base import ReadOnly, NameSpace, lock, islocked
+from ipalib.base import ReadOnly, lock, islocked
from ipalib.constants import DEFAULT_CONFIG
from ipapython.ipa_log_manager import (
log_mgr,
@@ -124,8 +124,6 @@ class Plugin(ReadOnly):
Base class for all plugins.
"""
- finalize_early = True
-
def __init__(self, api):
assert api is not None
self.__api = api
@@ -268,6 +266,50 @@ class Plugin(ReadOnly):
)
+class APINameSpace(collections.Mapping):
+ def __init__(self, api, base):
+ self.__api = api
+ self.__base = base
+ self.__name_seq = None
+ self.__name_set = None
+
+ def __enumerate(self):
+ if self.__name_set is None:
+ self.__name_set = frozenset(
+ name for name, klass in six.iteritems(self.__api._API__plugins)
+ if any(issubclass(b, self.__base) for b in klass.bases))
+
+ def __len__(self):
+ self.__enumerate()
+ return len(self.__name_set)
+
+ def __contains__(self, name):
+ self.__enumerate()
+ return name in self.__name_set
+
+ def __iter__(self):
+ if self.__name_seq is None:
+ self.__enumerate()
+ self.__name_seq = tuple(sorted(self.__name_set))
+ return iter(self.__name_seq)
+
+ def __getitem__(self, name):
+ name = getattr(name, '__name__', name)
+ klass = self.__api._API__plugins[name]
+ if not any(issubclass(b, self.__base) for b in klass.bases):
+ raise KeyError(name)
+ return self.__api._get(name)
+
+ def __call__(self):
+ return six.itervalues(self)
+
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError:
+ raise AttributeError(name)
+
+
class API(ReadOnly):
"""
Dynamic API object through which `Plugin` instances are accessed.
@@ -276,6 +318,7 @@ class API(ReadOnly):
def __init__(self):
super(API, self).__init__()
self.__plugins = {}
+ self.__instances = {}
self.__next = {}
self.__done = set()
self.env = Env()
@@ -628,33 +671,28 @@ class API(ReadOnly):
self.__do_if_not_done('load_plugins')
production_mode = self.is_production_mode()
- plugins = {}
plugin_info = {}
for base in self.bases:
name = base.__name__
- members = []
- for klass in self.__plugins.values():
+ for klass in six.itervalues(self.__plugins):
if not any(issubclass(b, base) for b in klass.bases):
continue
- try:
- instance = plugins[klass]
- except KeyError:
- instance = plugins[klass] = klass(self)
- members.append(instance)
plugin_info.setdefault(
'%s.%s' % (klass.__module__, klass.name),
[]).append(name)
+ if not self.env.plugins_on_demand:
+ self._get(klass.name)
if not production_mode:
assert not hasattr(self, name)
- setattr(self, name, NameSpace(members))
+ setattr(self, name, APINameSpace(self, base))
- for klass, instance in plugins.items():
+ for klass, instance in six.iteritems(self.__instances):
if not production_mode:
assert instance.api is self
- if klass.finalize_early or not self.env.plugins_on_demand:
+ if not self.env.plugins_on_demand:
instance.ensure_finalized()
if not production_mode:
assert islocked(instance)
@@ -665,6 +703,14 @@ class API(ReadOnly):
if not production_mode:
lock(self)
+ def _get(self, name):
+ klass = self.__plugins[name]
+ try:
+ instance = self.__instances[klass]
+ except KeyError:
+ instance = self.__instances[klass] = klass(self)
+ return instance
+
def get_plugin_next(self, klass):
if not callable(klass):
raise TypeError('plugin must be callable; got %r' % klass)
diff --git a/ipatests/test_ipalib/test_cli.py b/ipatests/test_ipalib/test_cli.py
index f03e155a7..c240b2b02 100644
--- a/ipatests/test_ipalib/test_cli.py
+++ b/ipatests/test_ipalib/test_cli.py
@@ -87,7 +87,7 @@ class DummyCommand(object):
class DummyAPI(object):
def __init__(self, cnt):
- self.__cmd = plugable.NameSpace(self.__cmd_iter(cnt))
+ self.__cmd = plugable.APINameSpace(self.__cmd_iter(cnt), DummyCommand)
def __get_cmd(self):
return self.__cmd
diff --git a/ipatests/test_ipalib/test_frontend.py b/ipatests/test_ipalib/test_frontend.py
index 58ab5f177..518dadcda 100644
--- a/ipatests/test_ipalib/test_frontend.py
+++ b/ipatests/test_ipalib/test_frontend.py
@@ -283,11 +283,11 @@ class test_Command(ClassChecker):
return False
o = self.cls(api)
o.finalize()
- assert type(o.args) is plugable.NameSpace
+ assert type(o.args) is NameSpace
assert len(o.args) == 0
args = ('destination', 'source?')
ns = self.get_instance(args=args).args
- assert type(ns) is plugable.NameSpace
+ assert type(ns) is NameSpace
assert len(ns) == len(args)
assert list(ns) == ['destination', 'source']
assert type(ns.destination) is parameters.Str
@@ -340,11 +340,11 @@ class test_Command(ClassChecker):
return False
o = self.cls(api)
o.finalize()
- assert type(o.options) is plugable.NameSpace
+ assert type(o.options) is NameSpace
assert len(o.options) == 1
options = ('target', 'files*')
ns = self.get_instance(options=options).options
- assert type(ns) is plugable.NameSpace
+ assert type(ns) is NameSpace
assert len(ns) == len(options) + 1
assert list(ns) == ['target', 'files', 'version']
assert type(ns.target) is parameters.Str
@@ -364,7 +364,7 @@ class test_Command(ClassChecker):
return False
inst = self.cls(api)
inst.finalize()
- assert type(inst.output) is plugable.NameSpace
+ assert type(inst.output) is NameSpace
assert list(inst.output) == ['result']
assert type(inst.output.result) is output.Output
@@ -945,7 +945,7 @@ class test_Object(ClassChecker):
methods_format = 'method_%d'
class FakeAPI(object):
- Method = plugable.NameSpace(
+ Method = NameSpace(
get_attributes(cnt, methods_format)
)
def __contains__(self, key):
@@ -965,7 +965,7 @@ class test_Object(ClassChecker):
assert read_only(o, 'api') is api
namespace = o.methods
- assert isinstance(namespace, plugable.NameSpace)
+ assert isinstance(namespace, NameSpace)
assert len(namespace) == cnt
f = methods_format
for i in range(cnt):
@@ -980,13 +980,13 @@ class test_Object(ClassChecker):
# Test params instance attribute
o = self.cls(api)
ns = o.params
- assert type(ns) is plugable.NameSpace
+ assert type(ns) is NameSpace
assert len(ns) == 0
class example(self.cls):
takes_params = ('banana', 'apple')
o = example(api)
ns = o.params
- assert type(ns) is plugable.NameSpace
+ assert type(ns) is NameSpace
assert len(ns) == 2, repr(ns)
assert list(ns) == ['banana', 'apple']
for p in ns():
@@ -1024,7 +1024,7 @@ class test_Object(ClassChecker):
assert pk.name == 'three'
assert pk.primary_key is True
assert o.params[2] is o.primary_key
- assert isinstance(o.params_minus_pk, plugable.NameSpace)
+ assert isinstance(o.params_minus_pk, NameSpace)
assert list(o.params_minus_pk) == ['one', 'two', 'four']
# Test with multiple primary_key: