diff --git a/ipalib/plugable.py b/ipalib/plugable.py index c3eb409b0..4032b574e 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -54,6 +54,23 @@ def check_identifier(name): raise errors.NameSpaceError(name, regex) +class Abstract(object): + __public__ = frozenset() + + @classmethod + def implements(cls, arg): + assert type(cls.__public__) is frozenset + if isinstance(arg, str): + return arg in cls.__public__ + if type(getattr(arg, '__public__', None)) is frozenset: + return cls.__public__.issuperset(arg.__public__) + if type(arg) is frozenset: + return cls.__public__.issuperset(arg) + raise TypeError( + "must be str, frozenset, or have frozenset '__public__' attribute" + ) + + class Plugin(object): """ Base class for all plugins. diff --git a/ipalib/public.py b/ipalib/public.py index 6f7f21540..1c6f9e7f8 100644 --- a/ipalib/public.py +++ b/ipalib/public.py @@ -102,7 +102,7 @@ class option(object): def __rules_iter(self): """ Iterates through the attributes in this instance to retrieve the - methods implemented validation rules. + methods implementing validation rules. """ for name in dir(self.__class__): if name.startswith('_'): @@ -117,6 +117,10 @@ class option(object): """ Returns a default or auto-completed value for this option. If no default is available, this method should return None. + + All the keywords are passed so it's possible to build an + auto-completed value from other options values, e.g., build 'initials' + from 'givenname' + 'sn'. """ return None @@ -177,11 +181,15 @@ class cmd(plugable.Plugin): self.options.validate(value) def default(self, **kw): + d = {} for opt in self.options: if opt.name not in kw: value = opt.default(**kw) if value is not None: - kw[opt.name] = value + d[opt.name] = value + assert not set(kw).intersection(d) + kw.update(d) + return kw def __call__(self, **kw): (args, kw) = self.normalize(*args, **kw) diff --git a/ipalib/tests/test_public.py b/ipalib/tests/test_public.py index f396eed28..49fbb17f2 100644 --- a/ipalib/tests/test_public.py +++ b/ipalib/tests/test_public.py @@ -63,13 +63,35 @@ def test_is_rule(): assert not is_rule(call(None)) -class test_option(): - def cls(self): - return public.option +class ClassChecker(object): + __cls = None + __subcls = None - def sub(self): + def __get_cls(self): + if self.__cls is None: + self.__cls = self._cls + return self.__cls + cls = property(__get_cls) + + def __get_subcls(self): + if self.__subcls is None: + self.__subcls = self.get_subcls() + return self.__subcls + subcls = property(__get_subcls) + + def get_subcls(self): + raise NotImplementedError( + self.__class__.__name__, + 'get_subcls()' + ) + + +class test_option(ClassChecker): + _cls = public.option + + def get_subcls(self): rule = public.rule - class int_opt(self.cls()): + class int_opt(self.cls): type = int @rule def rule_0(self, value): @@ -89,13 +111,12 @@ class test_option(): """ Perform some tests on the class (not an instance). """ - cls = self.cls() #assert issubclass(cls, plugable.ReadOnly) - assert type(cls.rules) is property + assert type(self.cls.rules) is property def test_normalize(self): - sub = self.sub() - i = sub() + assert 'normalize' in self.cls.__public__ + o = self.subcls() # Test with values that can't be converted: nope = ( '7.0' @@ -104,7 +125,7 @@ class test_option(): None, ) for val in nope: - e = raises(errors.NormalizationError, i.normalize, val) + e = raises(errors.NormalizationError, o.normalize, val) assert isinstance(e, errors.ValidationError) assert e.name == 'int_opt' assert e.value == val @@ -120,29 +141,36 @@ class test_option(): ' 7 ', ) for val in okay: - assert i.normalize(val) == 7 + assert o.normalize(val) == 7 + + def test_validate(self): + """ + Test the validate method. + """ + assert 'validate' in self.cls.__public__ + o = self.subcls() + o.validate(9) + for i in xrange(3): + e = raises(errors.RuleError, o.validate, i) + assert e.error == 'cannot be %d' % i + assert e.value == i def test_rules(self): """ Test the rules property. """ - o = self.sub()() + o = self.subcls() assert len(o.rules) == 3 def get_rule(i): return getattr(o, 'rule_%d' % i) rules = tuple(get_rule(i) for i in xrange(3)) assert o.rules == rules - def test_validation(self): - """ - Test the validation method. - """ - o = self.sub()() - o.validate(9) - for i in xrange(3): - e = raises(errors.RuleError, o.validate, i) - assert e.error == 'cannot be %d' % i - assert e.value == i + def test_default(self): + assert 'default' in self.cls.__public__ + assert self.cls().default() is None + +