diff options
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | ipalib/constants.py | 6 | ||||
-rw-r--r-- | ipalib/parameter.py | 36 | ||||
-rwxr-xr-x | make-test | 1 | ||||
-rw-r--r-- | tests/test_ipalib/test_parameter.py | 48 |
5 files changed, 85 insertions, 8 deletions
@@ -25,6 +25,8 @@ API chages before January 2009 simi-freeze: * Implement gettext service. + * Add ability to register pre-op, post-op plugins per command. + CLI - Prompt for password using getpass - Passed the param dict to output_for_cli() diff --git a/ipalib/constants.py b/ipalib/constants.py index 06ff99d5e..d028a0013 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -26,6 +26,12 @@ All constants centralised in one file. NULLS = (None, '', u'', tuple(), []) +TYPE_ERROR = '%s: need a %r; got %r (a %r)' + + +CALLABLE_ERROR = '%s: need a callable; got %r (a %r)' + + # Used for a tab (or indentation level) when formatting for CLI: CLI_TAB = ' ' # Two spaces diff --git a/ipalib/parameter.py b/ipalib/parameter.py index b75b8fbe5..d67eb5957 100644 --- a/ipalib/parameter.py +++ b/ipalib/parameter.py @@ -22,7 +22,7 @@ Parameter system for command plugins. """ from plugable import ReadOnly, lock, check_name -from constants import NULLS +from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR def parse_param_spec(spec): @@ -83,15 +83,42 @@ class Param(ReadOnly): required=(bool, True), multivalue=(bool, False), primary_key=(bool, False), - normalize=(callable, None), + normalizer=(callable, None), default=(None, None), default_from=(callable, None), flags=(frozenset, frozenset()), ) - def __init__(self, name, **overrides): + def __init__(self, name, kwargs, **overrides): self.param_spec = name self.name = check_name(name) + kwargs = dict(kwargs) + assert set(self.__kwargs).intersection(kwargs) == set() + kwargs.update(self.__kwargs) + for (key, (kind, default)) in kwargs.iteritems(): + value = overrides.get(key, default) + if value is None: + if kind is bool: + raise TypeError( + TYPE_ERROR % (key, bool, value, type(value)) + ) + else: + if ( + type(kind) is type and type(value) is not kind or + type(kind) is tuple and not isinstance(value, kind) + ): + raise TypeError( + TYPE_ERROR % (key, kind, value, type(value)) + ) + elif kind is callable and not callable(value): + raise TypeError( + CALLABLE_ERROR % (key, value, type(value)) + ) + if hasattr(self, key): + raise ValueError('kwarg %r conflicts with attribute on %s' % ( + key, self.__class__.__name__) + ) + setattr(self, key, value) lock(self) def normalize(self, value): @@ -120,7 +147,6 @@ class Param(ReadOnly): except StandardError: return value - def convert(self, value): if value in NULLS: return @@ -178,7 +204,7 @@ class Str(Param): def __init__(self, name, **overrides): self.type = unicode - super(Str, self).__init__(name, **overrides) + super(Str, self).__init__(name, {}, **overrides) def _convert_scalar(self, value, index=None): if type(value) in (self.type, int, float, bool): @@ -25,6 +25,7 @@ done if [ $failures ]; then echo "[ Ran under $runs version(s); FAILED under $failures version(s) ]" + echo "FAIL!" exit $failures else echo "[ Ran under $runs version(s); all OK ]" diff --git a/tests/test_ipalib/test_parameter.py b/tests/test_ipalib/test_parameter.py index 837bdfef3..725ce60ac 100644 --- a/tests/test_ipalib/test_parameter.py +++ b/tests/test_ipalib/test_parameter.py @@ -25,6 +25,7 @@ Test the `ipalib.parameter` module. from tests.util import raises, ClassChecker from tests.data import binary_bytes, utf8_bytes, unicode_str from ipalib import parameter +from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR def test_parse_param_spec(): @@ -49,24 +50,65 @@ class test_Param(ClassChecker): Test the `ipalib.parameter.Param.__init__` method. """ name = 'my_param' - o = self.cls(name) + o = self.cls(name, {}) assert o.name is name + # assert o.cli_name is name + assert o.doc == '' + assert o.required is True + assert o.multivalue is False + assert o.primary_key is False + assert o.normalizer is None + assert o.default is None + assert o.default_from is None + assert o.flags == frozenset() assert o.__islocked__() is True + kwarg = dict(convert=(callable, None)) + e = raises(ValueError, self.cls, name, kwarg) + assert str(e) == "kwarg 'convert' conflicts with attribute on Param" + class Subclass(self.cls): + pass + e = raises(ValueError, Subclass, name, kwarg) + assert str(e) == "kwarg 'convert' conflicts with attribute on Subclass" + kwargs = dict( + extra1=(bool, True), + extra2=(str, 'Hello'), + extra3=((int, float), 42), + extra4=(callable, lambda whatever: whatever + 7), + ) + # Check that we don't accept None if kind is bool: + e = raises(TypeError, self.cls, 'my_param', kwargs, extra1=None) + assert str(e) == TYPE_ERROR % ('extra1', bool, None, type(None)) + for (key, (kind, default)) in kwargs.items(): + o = self.cls('my_param', kwargs) + # Test with a type invalid for all: + value = object() + overrides = {key: value} + e = raises(TypeError, self.cls, 'my_param', kwargs, **overrides) + if kind is callable: + assert str(e) == CALLABLE_ERROR % (key, value, type(value)) + else: + assert str(e) == TYPE_ERROR % (key, kind, value, type(value)) + if kind is bool: + continue + # Test with None: + overrides = {key: None} + o = self.cls('my_param', kwargs, **overrides) def test_convert_scalar(self): """ Test the `ipalib.parameter.Param._convert_scalar` method. """ - o = self.cls('my_param') + o = self.cls('my_param', {}) e = raises(NotImplementedError, o._convert_scalar, 'some value') assert str(e) == 'Param._convert_scalar()' class Subclass(self.cls): pass - o = Subclass('my_param') + o = Subclass('my_param', {}) e = raises(NotImplementedError, o._convert_scalar, 'some value') assert str(e) == 'Subclass._convert_scalar()' + class test_Str(ClassChecker): """ Test the `ipalib.parameter.Str` class. |