diff options
-rw-r--r-- | ipalib/parameter.py | 85 | ||||
-rw-r--r-- | tests/test_ipalib/test_parameter.py | 55 |
2 files changed, 75 insertions, 65 deletions
diff --git a/ipalib/parameter.py b/ipalib/parameter.py index b071e9d57..6acf04830 100644 --- a/ipalib/parameter.py +++ b/ipalib/parameter.py @@ -21,6 +21,7 @@ Parameter system for command plugins. """ +from types import NoneType from plugable import ReadOnly, lock, check_name from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR @@ -178,44 +179,46 @@ class DefaultFrom(ReadOnly): class Param(ReadOnly): """ - Base class for all IPA types. + Base class for all parameters. """ - __kwargs = dict( - cli_name=(str, None), - doc=(str, ''), - required=(bool, True), - multivalue=(bool, False), - primary_key=(bool, False), - normalizer=(callable, None), - default=(None, None), - default_from=(callable, None), - flags=(frozenset, frozenset()), + kwargs = ( + ('cli_name', str, None), + ('doc', str, ''), + ('required', bool, True), + ('multivalue', bool, False), + ('primary_key', bool, False), + ('normalizer', callable, None), + ('default_from', callable, None), + ('flags', frozenset, frozenset()), + + # The 'default' kwarg gets appended in Param.__init__(): + # ('default', self.type, None), ) - def __init__(self, name, kwargs, **override): + # This is a dummy type so that most of the functionality of Param can be + # unit tested directly without always creating a subclass; however, real + # (direct) subclasses should *always* override this class attribute: + type = NoneType # This isn't very useful in the real world! + + def __init__(self, name, **kw): + assert type(self.type) is type self.param_spec = name - self.__override = dict(override) - if not ('required' in override or 'multivalue' in override): + self.__kw = dict(kw) + if not ('required' in kw or 'multivalue' in kw): (name, kw_from_spec) = parse_param_spec(name) - override.update(kw_from_spec) + kw.update(kw_from_spec) self.name = check_name(name) - if 'cli_name' not in override: - override['cli_name'] = self.name - df = override.get('default_from', None) + if kw.get('cli_name', None) is None: + kw['cli_name'] = self.name + df = kw.get('default_from', None) if callable(df) and not isinstance(df, DefaultFrom): - override['default_from'] = DefaultFrom(df) - kwargs = dict(kwargs) - assert set(self.__kwargs).intersection(kwargs) == set() - kwargs.update(self.__kwargs) - for (key, (kind, default)) in kwargs.iteritems(): - value = override.get(key, default) - if value is None: - if kind is bool: - raise TypeError( - TYPE_ERROR % (key, bool, value, type(value)) - ) - else: + kw['default_from'] = DefaultFrom(df) + self.__clonekw = kw + self.kwargs += (('default', self.type, None),) + for (key, kind, default) in self.kwargs: + value = kw.get(key, default) + if value is not None: if ( type(kind) is type and type(value) is not kind or @@ -275,7 +278,7 @@ class Param(ReadOnly): """ Normalize a scalar value. - This method is called once for each value in multivalue. + This method is called once for each value in a multivalue. """ if type(value) is not unicode: return value @@ -308,8 +311,6 @@ class Param(ReadOnly): ) - - class Bool(Param): """ @@ -333,15 +334,27 @@ class Bytes(Param): """ + type = str + + def __init__(self, name, **kw): + kwargs = dict( + minlength=(int, None), + maxlength=(int, None), + length=(int, None), + pattern=(str, None), + ) + + class Str(Param): """ """ - def __init__(self, name, **overrides): - self.type = unicode - super(Str, self).__init__(name, {}, **overrides) + type = unicode + + def __init__(self, name, **kw): + super(Str, self).__init__(name, **kw) def _convert_scalar(self, value, index=None): if type(value) in (self.type, int, float, bool): diff --git a/tests/test_ipalib/test_parameter.py b/tests/test_ipalib/test_parameter.py index 84e9fc0e9..f72c4e627 100644 --- a/tests/test_ipalib/test_parameter.py +++ b/tests/test_ipalib/test_parameter.py @@ -92,6 +92,8 @@ def test_parse_param_spec(): assert f('name?') == ('name', dict(required=False, multivalue=False)) assert f('name*') == ('name', dict(required=False, multivalue=True)) assert f('name+') == ('name', dict(required=True, multivalue=True)) + # Make sure other "funny" endings are treated special: + assert f('name^') == ('name^', dict(required=True, multivalue=False)) class test_Param(ClassChecker): @@ -105,72 +107,67 @@ 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.__islocked__() is True - # Test default values: - assert o.name is name + # Test default kwarg values: 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 is None assert o.default_from is None assert o.flags == frozenset() # Test that ValueError is raised when a kwarg from a subclass # conflicts with an attribute: - 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) + kwargs = self.cls.kwargs + ( + ('convert', callable, None), + ) + e = raises(ValueError, Subclass, name) assert str(e) == "kwarg 'convert' conflicts with attribute on Subclass" # Test type validation of keyword arguments: - kwargs = dict( - extra1=(bool, True), - extra2=(str, 'Hello'), - extra3=((int, float), 42), - extra4=(callable, lambda whatever: whatever + 7), - ) - # Note: 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) + class Subclass(self.cls): + kwargs = self.cls.kwargs + ( + ('extra1', bool, True), + ('extra2', str, 'Hello'), + ('extra3', (int, float), 42), + ('extra4', callable, lambda whatever: whatever + 7), + ) + o = Subclass('my_param') # Test with no **kw: + for (key, kind, default) in o.kwargs: # Test with a type invalid for all: value = object() - overrides = {key: value} - e = raises(TypeError, self.cls, 'my_param', kwargs, **overrides) + kw = {key: value} + e = raises(TypeError, Subclass, 'my_param', **kw) 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: # See note above - continue + # Test with None: - overrides = {key: None} - o = self.cls('my_param', kwargs, **overrides) + kw = {key: None} + Subclass('my_param', **kw) 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. |