summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO26
-rw-r--r--ipalib/__init__.py32
-rw-r--r--ipalib/cli.py6
-rw-r--r--ipalib/crud.py5
-rw-r--r--ipalib/errors2.py24
-rw-r--r--ipalib/frontend.py520
-rw-r--r--ipalib/ipa_types.py189
-rw-r--r--ipalib/parameters.py (renamed from ipalib/parameter.py)449
-rw-r--r--ipalib/plugins/f_automount.py76
-rw-r--r--ipalib/plugins/f_group.py38
-rw-r--r--ipalib/plugins/f_host.py47
-rw-r--r--ipalib/plugins/f_hostgroup.py34
-rw-r--r--ipalib/plugins/f_passwd.py16
-rw-r--r--ipalib/plugins/f_pwpolicy.py26
-rw-r--r--ipalib/plugins/f_ra.py10
-rw-r--r--ipalib/plugins/f_service.py25
-rw-r--r--ipalib/plugins/f_user.py67
-rw-r--r--tests/test_ipalib/test_frontend.py519
-rw-r--r--tests/test_ipalib/test_ipa_types.py430
-rw-r--r--tests/test_ipalib/test_parameter.py531
-rw-r--r--tests/test_ipalib/test_parameters.py944
-rw-r--r--tests/util.py23
22 files changed, 1674 insertions, 2363 deletions
diff --git a/TODO b/TODO
index a4b25fd8..56c8c00a 100644
--- a/TODO
+++ b/TODO
@@ -49,12 +49,38 @@ API chages before January 2009 simi-freeze:
for retrieving per-request dynamic environment variables.
+CRUD base classes:
+
+ * The Retrieve method should add in the common Flag('all') option for
+ retrieving all attributes.
+
+ * We probably need some LDAP centric crud method base classes, like
+ LDAPCreate, etc. Or other options it to have an LDAPObject base class and
+ have the crud Method plugins rely more on their corresponding Object plugin.
+
+ * Update the Retrieve, Update, Delete, and Search classes so that the utilize
+ the new Param.query kwarg (to turn off validation) when cloning params.
+
+
+Existing plugins:
+
+ * Many existing plugins that are doing crud-type operations aren't using the
+ Object + Method way of defining their parameters, and are therefore defining
+ the exact same parameter several times in a module. This should be fixed
+ one way or another... if there are deficiencies in the crud base classes,
+ they need to be improved.
+
+
Command Line interface:
* Finish textui plugin
* Make possible Enum values self-documenting
+ * All "comma-separated list of..." parameters should really be changed to
+ multivalue and have a flag that tells the CLI whether a multivalue should
+ be parsed as comma-separated.
+
Improve ease of plugin writting
- make "from ipalib import *" import everything a plugin writter will need
diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index e1ef09c1..e5aa65d6 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -405,13 +405,13 @@ Defining arguments and options for your command
You can define a command that will accept specific arguments and options.
For example:
->>> from ipalib import Param
+>>> from ipalib import Str
>>> class nudge(Command):
... """Takes one argument, one option"""
...
... takes_args = ['programmer']
...
-... takes_options = [Param('stuff', default=u'documentation')]
+... takes_options = [Str('stuff', default=u'documentation')]
...
... def execute(self, programmer, **kw):
... return '%s, go write more %s!' % (programmer, kw['stuff'])
@@ -420,9 +420,9 @@ For example:
>>> api.env.in_server = True
>>> api.register(nudge)
>>> api.finalize()
->>> api.Command.nudge('Jason')
+>>> api.Command.nudge(u'Jason')
u'Jason, go write more documentation!'
->>> api.Command.nudge('Jason', stuff='unit tests')
+>>> api.Command.nudge(u'Jason', stuff=u'unit tests')
u'Jason, go write more unit tests!'
The ``args`` and ``options`` attributes are `plugable.NameSpace` instances
@@ -431,11 +431,11 @@ containing a command's arguments and options, respectively, as you can see:
>>> list(api.Command.nudge.args) # Iterates through argument names
['programmer']
>>> api.Command.nudge.args.programmer
-Param('programmer')
+Str('programmer')
>>> list(api.Command.nudge.options) # Iterates through option names
['stuff']
>>> api.Command.nudge.options.stuff
-Param('stuff', default=u'documentation')
+Str('stuff', default=u'documentation')
>>> api.Command.nudge.options.stuff.default
u'documentation'
@@ -451,7 +451,7 @@ NameSpace(<2 members>, sort=False)
When calling a command, its positional arguments can also be provided as
keyword arguments, and in any order. For example:
->>> api.Command.nudge(stuff='lines of code', programmer='Jason')
+>>> api.Command.nudge(stuff=u'lines of code', programmer=u'Jason')
u'Jason, go write more lines of code!'
When a command plugin is called, the values supplied for its parameters are
@@ -465,20 +465,20 @@ here is a quick teaser:
... takes_options = [
... 'first',
... 'last',
-... Param('nick',
-... normalize=lambda value: value.lower(),
+... Str('nick',
+... normalizer=lambda value: value.lower(),
... default_from=lambda first, last: first[0] + last,
... ),
-... Param('points', type=Int(), default=0),
+... Int('points', default=0),
... ]
...
>>> cp = create_player()
>>> cp.finalize()
->>> cp.convert(points=" 1000 ")
+>>> cp.convert(points=u' 1000 ')
{'points': 1000}
>>> cp.normalize(nick=u'NickName')
{'nick': u'nickname'}
->>> cp.get_default(first='Jason', last='DeRose')
+>>> cp.get_default(first=u'Jason', last=u'DeRose')
{'nick': u'jderose', 'points': 0}
For the full details on the parameter system, see the
@@ -575,7 +575,7 @@ For example, say we setup a command like this:
...
... takes_args = ['key?']
...
-... takes_options = [Param('reverse', type=Bool(), default=False)]
+... takes_options = [Flag('reverse')]
...
... def execute(self, key, **options):
... items = dict(
@@ -643,7 +643,7 @@ show-items:
Lastly, providing a ``key`` would result in the following:
->>> result = api.Command.show_items('city')
+>>> result = api.Command.show_items(u'city')
>>> api.Command.show_items.output_for_cli(textui, result, 'city', reverse=False)
city = 'Berlin'
@@ -874,8 +874,8 @@ import plugable
from backend import Backend, Context
from frontend import Command, LocalOrRemote, Application
from frontend import Object, Method, Property
-from ipa_types import Bool, Int, Unicode, Enum
-from frontend import Param, DefaultFrom
+from parameters import DefaultFrom, Bool, Flag, Int, Float, Bytes, Str, Password
+
def create_api(mode='dummy'):
"""
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 442e5061..5cf68852 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -36,9 +36,9 @@ import frontend
import backend
import errors
import plugable
-import ipa_types
import util
from constants import CLI_TAB
+from parameters import Password
def to_cli(name):
@@ -700,7 +700,7 @@ class CLI(object):
result = cmd(**kw)
if callable(cmd.output_for_cli):
for param in cmd.params():
- if param.ispassword():
+ if isinstance(param, Password):
try:
del kw[param.name]
except KeyError:
@@ -801,7 +801,7 @@ class CLI(object):
)
if 'password' in option.flags:
kw['action'] = 'store_true'
- elif isinstance(option.type, ipa_types.Bool):
+ elif option.type is bool:
if option.default is True:
kw['action'] = 'store_false'
else:
diff --git a/ipalib/crud.py b/ipalib/crud.py
index 867f9fe1..345fc270 100644
--- a/ipalib/crud.py
+++ b/ipalib/crud.py
@@ -50,13 +50,14 @@ class Del(frontend.Method):
for option in self.takes_options:
yield option
+
class Mod(frontend.Method):
def get_args(self):
yield self.obj.primary_key
def get_options(self):
for param in self.obj.params_minus_pk():
- yield param.__clone__(required=False)
+ yield param.clone(required=False, query=True)
for option in self.takes_options:
yield option
@@ -67,7 +68,7 @@ class Find(frontend.Method):
def get_options(self):
for param in self.obj.params_minus_pk():
- yield param.__clone__(required=False)
+ yield param.clone(required=False, query=True)
for option in self.takes_options:
yield option
diff --git a/ipalib/errors2.py b/ipalib/errors2.py
index b052882d..4c8acd5d 100644
--- a/ipalib/errors2.py
+++ b/ipalib/errors2.py
@@ -465,25 +465,49 @@ class OptionError(InvocationError):
class RequirementError(InvocationError):
"""
**3005** Raised when a required parameter is not provided.
+
+ For example:
+
+ >>> raise RequirementError(name='givenname')
+ Traceback (most recent call last):
+ ...
+ RequirementError: 'givenname' is required
"""
errno = 3005
+ format = _('%(name)r is required')
class ConversionError(InvocationError):
"""
**3006** Raised when parameter value can't be converted to correct type.
+
+ For example:
+
+ >>> raise ConversionError(name='age', error='must be an integer')
+ Traceback (most recent call last):
+ ...
+ ConversionError: invalid 'age': must be an integer
"""
errno = 3006
+ format = _('invalid %(name)r: %(error)s')
class ValidationError(InvocationError):
"""
**3007** Raised when a parameter value fails a validation rule.
+
+ For example:
+
+ >>> raise ValidationError(name='sn', error='can be at most 128 characters')
+ Traceback (most recent call last):
+ ...
+ ValidationError: invalid 'sn': can be at most 128 characters
"""
errno = 3007
+ format = _('invalid %(name)r: %(error)s')
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index c614e547..b30205fe 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -27,7 +27,7 @@ import plugable
from plugable import lock, check_name
import errors
from errors import check_type, check_isinstance, raise_TypeError
-import ipa_types
+from parameters import create_param, Param, Str, Flag
from util import make_repr
@@ -42,453 +42,6 @@ def is_rule(obj):
return callable(obj) and getattr(obj, RULE_FLAG, False) is True
-class DefaultFrom(plugable.ReadOnly):
- """
- Derive a default value from other supplied values.
-
- For example, say you wanted to create a default for the user's login from
- the user's first and last names. It could be implemented like this:
-
- >>> login = DefaultFrom(lambda first, last: first[0] + last)
- >>> login(first='John', last='Doe')
- 'JDoe'
-
- If you do not explicitly provide keys when you create a DefaultFrom
- instance, the keys are implicitly derived from your callback by
- inspecting ``callback.func_code.co_varnames``. The keys are available
- through the ``DefaultFrom.keys`` instance attribute, like this:
-
- >>> login.keys
- ('first', 'last')
-
- The callback is available through the ``DefaultFrom.callback`` instance
- attribute, like this:
-
- >>> login.callback # doctest:+ELLIPSIS
- <function <lambda> at 0x...>
- >>> login.callback.func_code.co_varnames # The keys
- ('first', 'last')
-
- The keys can be explicitly provided as optional positional arguments after
- the callback. For example, this is equivalent to the ``login`` instance
- above:
-
- >>> login2 = DefaultFrom(lambda a, b: a[0] + b, 'first', 'last')
- >>> login2.keys
- ('first', 'last')
- >>> login2.callback.func_code.co_varnames # Not the keys
- ('a', 'b')
- >>> login2(first='John', last='Doe')
- 'JDoe'
-
- If any keys are missing when calling your DefaultFrom instance, your
- callback is not called and None is returned. For example:
-
- >>> login(first='John', lastname='Doe') is None
- True
- >>> login() is None
- True
-
- Any additional keys are simply ignored, like this:
-
- >>> login(last='Doe', first='John', middle='Whatever')
- 'JDoe'
-
- As above, because `DefaultFrom.__call__` takes only pure keyword
- arguments, they can be supplied in any order.
-
- Of course, the callback need not be a lambda expression. This third
- example is equivalent to both the ``login`` and ``login2`` instances
- above:
-
- >>> def get_login(first, last):
- ... return first[0] + last
- ...
- >>> login3 = DefaultFrom(get_login)
- >>> login3.keys
- ('first', 'last')
- >>> login3.callback.func_code.co_varnames
- ('first', 'last')
- >>> login3(first='John', last='Doe')
- 'JDoe'
- """
-
- def __init__(self, callback, *keys):
- """
- :param callback: The callable to call when all keys are present.
- :param keys: Optional keys used for source values.
- """
- if not callable(callback):
- raise TypeError('callback must be callable; got %r' % callback)
- self.callback = callback
- if len(keys) == 0:
- fc = callback.func_code
- self.keys = fc.co_varnames[:fc.co_argcount]
- else:
- self.keys = keys
- for key in self.keys:
- if type(key) is not str:
- raise_TypeError(key, str, 'keys')
- lock(self)
-
- def __call__(self, **kw):
- """
- If all keys are present, calls the callback; otherwise returns None.
-
- :param kw: The keyword arguments.
- """
- vals = tuple(kw.get(k, None) for k in self.keys)
- if None in vals:
- return
- try:
- return self.callback(*vals)
- except StandardError:
- pass
-
-
-def parse_param_spec(spec):
- """
- Parse a param spec into to (name, kw).
-
- The ``spec`` string determines the param name, whether the param is
- required, and whether the param is multivalue according the following
- syntax:
-
- ====== ===== ======== ==========
- Spec Name Required Multivalue
- ====== ===== ======== ==========
- 'var' 'var' True False
- 'var?' 'var' False False
- 'var*' 'var' False True
- 'var+' 'var' True True
- ====== ===== ======== ==========
-
- For example,
-
- >>> parse_param_spec('login')
- ('login', {'required': True, 'multivalue': False})
- >>> parse_param_spec('gecos?')
- ('gecos', {'required': False, 'multivalue': False})
- >>> parse_param_spec('telephone_numbers*')
- ('telephone_numbers', {'required': False, 'multivalue': True})
- >>> parse_param_spec('group+')
- ('group', {'required': True, 'multivalue': True})
-
- :param spec: A spec string.
- """
- if type(spec) is not str:
- raise_TypeError(spec, str, 'spec')
- if len(spec) < 2:
- raise ValueError(
- 'param spec must be at least 2 characters; got %r' % spec
- )
- _map = {
- '?': dict(required=False, multivalue=False),
- '*': dict(required=False, multivalue=True),
- '+': dict(required=True, multivalue=True),
- }
- end = spec[-1]
- if end in _map:
- return (spec[:-1], _map[end])
- return (spec, dict(required=True, multivalue=False))
-
-
-class Param(plugable.ReadOnly):
- """
- A parameter accepted by a `Command`.
-
- ============ ================= ==================
- Keyword Type Default
- ============ ================= ==================
- cli_name str defaults to name
- type ipa_type.Type ipa_type.Unicode()
- doc str ""
- required bool True
- multivalue bool False
- primary_key bool False
- normalize callable None
- default same as type.type None
- default_from callable None
- flags frozenset frozenset()
- ============ ================= ==================
- """
- __nones = (None, '', tuple(), [])
- __defaults = dict(
- cli_name=None,
- type=ipa_types.Unicode(),
- doc='',
- required=True,
- multivalue=False,
- primary_key=False,
- normalize=None,
- default=None,
- default_from=None,
- flags=frozenset(),
- rules=tuple(),
- )
-
- def __init__(self, name, **override):
- self.__param_spec = name
- self.__override = override
- self.__kw = dict(self.__defaults)
- if not ('required' in override or 'multivalue' in override):
- (name, kw_from_spec) = parse_param_spec(name)
- self.__kw.update(kw_from_spec)
- self.__kw['cli_name'] = name
- if not set(self.__kw).issuperset(override):
- extra = sorted(set(override) - set(self.__kw))
- raise TypeError(
- 'Param.__init__() takes no such kwargs: %s' % ', '.join(extra)
- )
- self.__kw.update(override)
- self.name = check_name(name)
- self.cli_name = check_name(self.__kw.get('cli_name', name))
- self.type = self.__check_isinstance(ipa_types.Type, 'type')
- self.doc = self.__check_type(str, 'doc')
- self.required = self.__check_type(bool, 'required')
- self.multivalue = self.__check_type(bool, 'multivalue')
- self.default = self.__kw['default']
- df = self.__kw['default_from']
- if callable(df) and not isinstance(df, DefaultFrom):
- df = DefaultFrom(df)
- self.default_from = check_type(df, DefaultFrom, 'default_from',
- allow_none=True
- )
- self.flags = frozenset(self.__kw['flags'])
- self.__normalize = self.__kw['normalize']
- self.rules = self.__check_type(tuple, 'rules')
- self.all_rules = (self.type.validate,) + self.rules
- self.primary_key = self.__check_type(bool, 'primary_key')
- lock(self)
-
- def ispassword(self):
- """
- Return ``True`` is this Param is a password.
- """
- return 'password' in self.flags
-
- def __clone__(self, **override):
- """
- Return a new `Param` instance similar to this one.
- """
- kw = dict(self.__kw)
- kw.update(override)
- return self.__class__(self.name, **kw)
-
- def __check_type(self, type_, name, allow_none=False):
- value = self.__kw[name]
- return check_type(value, type_, name, allow_none)
-
- def __check_isinstance(self, type_, name, allow_none=False):
- value = self.__kw[name]
- return check_isinstance(value, type_, name, allow_none)
-
- def __dispatch(self, value, scalar):
- """
- Helper method used by `normalize` and `convert`.
- """
- if value in self.__nones:
- return
- if self.multivalue:
- if type(value) in (tuple, list):
- return tuple(
- scalar(v, i) for (i, v) in enumerate(value)
- )
- return (scalar(value, 0),) # tuple
- return scalar(value)
-
- def __normalize_scalar(self, value, index=None):
- """
- Normalize a scalar value.
-
- This method is called once with each value in multivalue.
- """
- if not isinstance(value, basestring):
- return value
- try:
- return self.__normalize(value)
- except StandardError:
- return value
-
- def normalize(self, value):
- """
- Normalize ``value`` using normalize callback.
-
- For example:
-
- >>> param = Param('telephone',
- ... normalize=lambda value: value.replace('.', '-')
- ... )
- >>> param.normalize('800.123.4567')
- '800-123-4567'
-
- If this `Param` instance does not have a normalize callback,
- ``value`` is returned unchanged.
-
- If this `Param` instance has a normalize callback and ``value`` is
- a basestring, the normalize callback is called and its return value
- is returned.
-
- If ``value`` is not a basestring, or if an exception is caught
- when calling the normalize callback, ``value`` is returned unchanged.
-
- :param value: A proposed value for this parameter.
- """
- if self.__normalize is None:
- return value
- return self.__dispatch(value, self.__normalize_scalar)
-
- def __convert_scalar(self, value, index=None):
- """
- Convert a scalar value.
-
- This method is called once with each value in multivalue.
- """
- if value in self.__nones:
- return
- converted = self.type(value)
- if converted is None:
- raise errors.ConversionError(
- self.name, value, self.type, index=index
- )
- return converted
-
- def convert(self, value):
- """
- Convert/coerce ``value`` to Python type for this `Param`.
-
- For example:
-
- >>> param = Param('an_int', type=ipa_types.Int())
- >>> param.convert(7.2)
- 7
- >>> param.convert(" 7 ")
- 7
-
- If ``value`` can not be converted, ConversionError is raised, which
- is as subclass of ValidationError.
-
- If ``value`` is None, conversion is not attempted and None is
- returned.
-
- :param value: A proposed value for this parameter.
- """
- return self.__dispatch(value, self.__convert_scalar)
-
- def __validate_scalar(self, value, index=None):
- """
- Validate a scalar value.
-
- This method is called once with each value in multivalue.
- """
- if type(value) is not self.type.type:
- raise_TypeError(value, self.type.type, 'value')
- for rule in self.rules:
- error = rule(value)
- if error is not None:
- raise errors.RuleError(
- self.name, value, error, rule, index=index
- )
-
- def validate(self, value):
- """
- Check validity of a value.
-
- Each validation rule is called in turn and if any returns and error,
- RuleError is raised, which is a subclass of ValidationError.
-
- :param value: A proposed value for this parameter.
- """
- if value is None:
- if self.required:
- raise errors.RequirementError(self.name)
- return
- if self.multivalue:
- if type(value) is not tuple:
- raise_TypeError(value, tuple, 'value')
- for (i, v) in enumerate(value):
- self.__validate_scalar(v, i)
- else:
- self.__validate_scalar(value)
-
- def get_default(self, **kw):
- """
- Return a default value for this parameter.
-
- If this `Param` instance does not have a default_from() callback, this
- method always returns the static Param.default instance attribute.
-
- On the other hand, if this `Param` instance has a default_from()
- callback, the callback is called and its return value is returned
- (assuming that value is not None).
-
- If the default_from() callback returns None, or if an exception is
- caught when calling the default_from() callback, the static
- Param.default instance attribute is returned.
-
- :param kw: Optional keyword arguments to pass to default_from().
- """
- if self.default_from is not None:
- default = self.default_from(**kw)
- if default is not None:
- try:
- return self.convert(self.normalize(default))
- except errors.ValidationError:
- return None
- return self.default
-
- def get_values(self):
- """
- Return a tuple of possible values.
-
- For enumerable types, a tuple containing the possible values is
- returned. For all other types, an empty tuple is returned.
- """
- if self.type.name in ('Enum', 'CallbackEnum'):
- return self.type.values
- return tuple()
-
- def __call__(self, value, **kw):
- if value in self.__nones:
- value = self.get_default(**kw)
- else:
- value = self.convert(self.normalize(value))
- self.validate(value)
- return value
-
- def __repr__(self):
- """
- Return an expresion that could construct this `Param` instance.
- """
- return make_repr(
- self.__class__.__name__,
- self.__param_spec,
- **self.__override
- )
-
-
-def create_param(spec):
- """
- Create a `Param` instance from a param spec.
-
- If ``spec`` is a `Param` instance, ``spec`` is returned unchanged.
-
- If ``spec`` is an str instance, then ``spec`` is parsed and an
- appropriate `Param` instance is created and returned.
-
- See `parse_param_spec` for the definition of the spec syntax.
-
- :param spec: A spec string or a `Param` instance.
- """
- if type(spec) is Param:
- return spec
- if type(spec) is not str:
- raise TypeError(
- 'create_param() takes %r or %r; got %r' % (str, Param, spec)
- )
- return Param(spec)
-
-
class Command(plugable.Plugin):
"""
A public IPA atomic operation.
@@ -499,7 +52,8 @@ class Command(plugable.Plugin):
Plugins that subclass from Command are registered in the ``api.Command``
namespace. For example:
- >>> api = plugable.API(Command)
+ >>> from ipalib import create_api
+ >>> api = create_api()
>>> class my_command(Command):
... pass
...
@@ -607,14 +161,14 @@ class Command(plugable.Plugin):
>>> class my_command(Command):
... takes_options = (
- ... Param('first', normalize=lambda value: value.lower()),
+ ... Param('first', normalizer=lambda value: value.lower()),
... Param('last'),
... )
...
>>> c = my_command()
>>> c.finalize()
- >>> c.normalize(first='JOHN', last='DOE')
- {'last': 'DOE', 'first': 'john'}
+ >>> c.normalize(first=u'JOHN', last=u'DOE')
+ {'last': u'DOE', 'first': u'john'}
"""
return dict(
(k, self.params[k].normalize(v)) for (k, v) in kw.iteritems()
@@ -624,10 +178,10 @@ class Command(plugable.Plugin):
"""
Return a dictionary of values converted to correct type.
- >>> from ipalib import ipa_types
+ >>> from ipalib import Int
>>> class my_command(Command):
... takes_args = (
- ... Param('one', type=ipa_types.Int()),
+ ... Int('one'),
... 'two',
... )
...
@@ -646,14 +200,15 @@ class Command(plugable.Plugin):
For example:
+ >>> from ipalib import Str
>>> class my_command(Command):
- ... takes_args = [Param('color', default='Red')]
+ ... takes_args = [Str('color', default=u'Red')]
...
>>> c = my_command()
>>> c.finalize()
>>> c.get_default()
- {'color': 'Red'}
- >>> c.get_default(color='Yellow')
+ {'color': u'Red'}
+ >>> c.get_default(color=u'Yellow')
{}
"""
return dict(self.__get_default_iter(kw))
@@ -663,13 +218,12 @@ class Command(plugable.Plugin):
Generator method used by `Command.get_default`.
"""
for param in self.params():
- if kw.get(param.name, None) is None:
- if param.required:
- yield (param.name, param.get_default(**kw))
- elif isinstance(param.type, ipa_types.Bool):
- yield (param.name, param.default)
- else:
- yield (param.name, None)
+ if param.name in kw:
+ continue
+ if param.required or param.autofill:
+ default = param.get_default(**kw)
+ if default is not None:
+ yield (param.name, default)
def validate(self, **kw):
"""
@@ -810,7 +364,7 @@ class LocalOrRemote(Command):
"""
takes_options = (
- Param('server?', type=ipa_types.Bool(), default=False,
+ Flag('server?',
doc='Forward to server instead of running locally',
),
)
@@ -900,7 +454,7 @@ class Object(plugable.Plugin):
if type(spec) is str:
key = spec.rstrip('?*+')
else:
- assert type(spec) is Param
+ assert isinstance(spec, Param)
key = spec.name
if key in props:
yield props.pop(key).param
@@ -1009,7 +563,8 @@ class Method(Attribute, Command):
say you created a `Method` plugin and its corresponding `Object` plugin
like this:
- >>> api = plugable.API(Command, Object, Method, Property)
+ >>> from ipalib import create_api
+ >>> api = create_api()
>>> class user_add(Method):
... def run(self):
... return 'Added the user!'
@@ -1064,29 +619,26 @@ class Property(Attribute):
'type',
)).union(Attribute.__public__)
- type = ipa_types.Unicode()
- required = False
- multivalue = False
+ klass = Str
default = None
default_from = None
- normalize = None
+ normalizer = None
def __init__(self):
super(Property, self).__init__()
- self.rules = tuple(sorted(
- self.__rules_iter(),
- key=lambda f: getattr(f, '__name__'),
- ))
- self.param = Param(self.attr_name,
- type=self.type,
- doc=self.doc,
- required=self.required,
- multivalue=self.multivalue,
- default=self.default,
- default_from=self.default_from,
- rules=self.rules,
- normalize=self.normalize,
+ self.rules = tuple(
+ sorted(self.__rules_iter(), key=lambda f: getattr(f, '__name__'))
+ )
+ self.kwargs = tuple(
+ sorted(self.__kw_iter(), key=lambda keyvalue: keyvalue[0])
)
+ kw = dict(self.kwargs)
+ self.param = self.klass(self.attr_name, *self.rules, **kw)
+
+ def __kw_iter(self):
+ for (key, kind, default) in self.klass.kwargs:
+ if getattr(self, key, None) is not None:
+ yield (key, getattr(self, key))
def __rules_iter(self):
"""
diff --git a/ipalib/ipa_types.py b/ipalib/ipa_types.py
deleted file mode 100644
index 583cceed..00000000
--- a/ipalib/ipa_types.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Authors:
-# Jason Gerard DeRose <jderose@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; version 2 only
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-Type system for coercing and normalizing input values.
-"""
-
-import re
-from plugable import ReadOnly, lock
-import errors
-
-
-def check_min_max(min_value, max_value, min_name, max_name):
- assert type(min_name) is str, 'min_name must be an str'
- assert type(max_name) is str, 'max_name must be an str'
- for (name, value) in [(min_name, min_value), (max_name, max_value)]:
- if not (value is None or type(value) is int):
- raise TypeError(
- '%s must be an int or None, got: %r' % (name, value)
- )
- if None not in (min_value, max_value) and min_value > max_value:
- d = dict(
- k0=min_name,
- v0=min_value,
- k1=max_name,
- v1=max_value,
- )
- raise ValueError(
- '%(k0)s > %(k1)s: %(k0)s=%(v0)r, %(k1)s=%(v1)r' % d
- )
-
-
-class Type(ReadOnly):
- """
- Base class for all IPA types.
- """
-
- def __init__(self, type_):
- if type(type_) is not type:
- raise TypeError('%r is not %r' % (type(type_), type))
- allowed = (bool, int, float, unicode)
- if type_ not in allowed:
- raise ValueError('not an allowed type: %r' % type_)
- self.type = type_
- # FIXME: This should be replaced with a more user friendly message
- # as this is what is returned to the user.
- self.conversion_error = 'Must be a %r' % self.type
- lock(self)
-
- def __get_name(self):
- """
- Convenience property to return the class name.
- """
- return self.__class__.__name__
- name = property(__get_name)
-
- def convert(self, value):
- try:
- return self.type(value)
- except (TypeError, ValueError):
- return None
-
- def validate(self, value):
- pass
-
- def __call__(self, value):
- if value is None:
- raise TypeError('value cannot be None')
- if type(value) is self.type:
- return value
- return self.convert(value)
-
-
-class Bool(Type):
- def __init__(self, true='Yes', false='No'):
- if true is None:
- raise TypeError('`true` cannot be None')
- if false is None:
- raise TypeError('`false` cannot be None')
- if true == false:
- raise ValueError(
- 'cannot be equal: true=%r, false=%r' % (true, false)
- )
- self.true = true
- self.false = false
- super(Bool, self).__init__(bool)
-
- def convert(self, value):
- if value == self.true:
- return True
- if value == self.false:
- return False
- return None
-
-
-class Int(Type):
- def __init__(self, min_value=None, max_value=None):
- check_min_max(min_value, max_value, 'min_value', 'max_value')
- self.min_value = min_value
- self.max_value = max_value
- super(Int, self).__init__(int)
-
- def validate(self, value):
- if type(value) is not self.type:
- return 'Must be an integer'
- if self.min_value is not None and value < self.min_value:
- return 'Cannot be smaller than %d' % self.min_value
- if self.max_value is not None and value > self.max_value:
- return 'Cannot be larger than %d' % self.max_value
-
-
-class Unicode(Type):
- def __init__(self, min_length=None, max_length=None, pattern=None):
- check_min_max(min_length, max_length, 'min_length', 'max_length')
- if min_length is not None and min_length < 0:
- raise ValueError('min_length must be >= 0, got: %r' % min_length)
- if max_length is not None and max_length < 1:
- raise ValueError('max_length must be >= 1, got: %r' % max_length)
- if not (pattern is None or isinstance(pattern, basestring)):
- raise TypeError(
- 'pattern must be a basestring or None, got: %r' % pattern
- )
- self.min_length = min_length
- self.max_length = max_length
- self.pattern = pattern
- if pattern is None:
- self.regex = None
- else:
- self.regex = re.compile(pattern)
- super(Unicode, self).__init__(unicode)
-
- def convert(self, value):
- assert type(value) not in (list, tuple)
- try:
- return self.type(value)
- except (TypeError, ValueError):
- return None
-
- def validate(self, value):
- if type(value) is not self.type:
- return 'Must be a string'
-
- if self.regex and self.regex.match(value) is None:
- return 'Must match %r' % self.pattern
-
- if self.min_length is not None and len(value) < self.min_length:
- return 'Must be at least %d characters long' % self.min_length
-
- if self.max_length is not None and len(value) > self.max_length:
- return 'Can be at most %d characters long' % self.max_length
-
-
-class Enum(Type):
- def __init__(self, *values):
- if len(values) < 1:
- raise ValueError('%s requires at least one value' % self.name)
- type_ = type(values[0])
- if type_ not in (unicode, int, float):
- raise TypeError(
- '%r: %r not unicode, int, nor float' % (values[0], type_)
- )
- for val in values[1:]:
- if type(val) is not type_:
- raise TypeError('%r: %r is not %r' % (val, type(val), type_))
- self.values = values
- self.frozenset = frozenset(values)
- super(Enum, self).__init__(type_)
-
- def validate(self, value):
- if type(value) is not self.type:
- return 'Incorrect type'
- if value not in self.frozenset:
- return 'Invalid value'
diff --git a/ipalib/parameter.py b/ipalib/parameters.py
index 204fda66..fd693e71 100644
--- a/ipalib/parameter.py
+++ b/ipalib/parameters.py
@@ -19,15 +19,25 @@
"""
Parameter system for command plugins.
+
+TODO:
+
+ * Change rule call signature to rule(_, value, **kw) so that rules can also
+ validate relative to other parameter values (e.g., login name as it relates
+ to first name and last name)
+
+ * Add the _rule_pattern() methods to `Bytes` and `Str`
+
+ * Add maxvalue, minvalue kwargs and rules to `Int` and `Float`
"""
from types import NoneType
from util import make_repr
from request import ugettext
from plugable import ReadOnly, lock, check_name
+from errors2 import ConversionError, RequirementError, ValidationError
from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR
-
class DefaultFrom(ReadOnly):
"""
Derive a default value from other supplied values.
@@ -141,10 +151,10 @@ class DefaultFrom(ReadOnly):
def parse_param_spec(spec):
"""
- Parse a param spec into to (name, kw).
+ Parse shorthand ``spec`` into to ``(name, kw)``.
- The ``spec`` string determines the param name, whether the param is
- required, and whether the param is multivalue according the following
+ The ``spec`` string determines the parameter name, whether the parameter is
+ required, and whether the parameter is multivalue according the following
syntax:
====== ===== ======== ==========
@@ -188,6 +198,13 @@ def parse_param_spec(spec):
return (spec, dict(required=True, multivalue=False))
+__messages = set()
+
+def _(message):
+ __messages.add(message)
+ return message
+
+
class Param(ReadOnly):
"""
Base class for all parameters.
@@ -198,6 +215,9 @@ class Param(ReadOnly):
# (direct) subclass must *always* override this class attribute:
type = NoneType # Ouch, this wont be very useful in the real world!
+ # Subclasses should override this with something more specific:
+ type_error = _('incorrect type')
+
kwargs = (
('cli_name', str, None),
('label', callable, None),
@@ -206,7 +226,10 @@ class Param(ReadOnly):
('multivalue', bool, False),
('primary_key', bool, False),
('normalizer', callable, None),
- ('default_from', callable, None),
+ ('default_from', DefaultFrom, None),
+ ('create_default', callable, None),
+ ('autofill', bool, False),
+ ('query', bool, False),
('flags', frozenset, frozenset()),
# The 'default' kwarg gets appended in Param.__init__():
@@ -280,6 +303,20 @@ class Param(ReadOnly):
class_rules.append(getattr(self, rule_name))
check_name(self.cli_name)
+ # Check that only default_from or create_default was provided:
+ assert not hasattr(self, '_get_default'), self.nice
+ if callable(self.default_from):
+ if callable(self.create_default):
+ raise ValueError(
+ '%s: cannot have both %r and %r' % (
+ self.nice, 'default_from', 'create_default')
+ )
+ self._get_default = self.default_from
+ elif callable(self.create_default):
+ self._get_default = self.create_default
+ else:
+ self._get_default = None
+
# Check that all the rules are callable
self.class_rules = tuple(class_rules)
self.rules = rules
@@ -303,6 +340,25 @@ class Param(ReadOnly):
**self.__kw
)
+ def __call__(self, value, **kw):
+ """
+ One stop shopping.
+ """
+ if value in NULLS:
+ value = self.get_default(**kw)
+ else:
+ value = self.convert(self.normalize(value))
+ self.validate(value)
+ return value
+
+ def clone(self, **overrides):
+ """
+ Return a new `Param` instance similar to this one.
+ """
+ kw = dict(self.__clonekw)
+ kw.update(overrides)
+ return self.__class__(self.name, **kw)
+
def get_label(self):
"""
Return translated label using `request.ugettext`.
@@ -383,8 +439,8 @@ class Param(ReadOnly):
multivalue parameter. For example:
>>> multi = Str('my_multi', multivalue=True)
- >>> multi.convert([True, '', 17, None, False])
- (u'True', u'17', u'False')
+ >>> multi.convert([1.5, '', 17, None, u'Hello'])
+ (u'1.5', u'17', u'Hello')
>>> multi.convert([None, u'']) is None # Filters to an empty list
True
@@ -393,8 +449,8 @@ class Param(ReadOnly):
>>> multi.convert(42) # Called with a scalar value
(u'42',)
- >>> multi.convert([True, False]) # Called with a list value
- (u'True', u'False')
+ >>> multi.convert([0, 1]) # Called with a list value
+ (u'0', u'1')
Note that how values are converted (and from what types they will be
converted) completely depends upon how a subclass implements its
@@ -410,7 +466,7 @@ class Param(ReadOnly):
value = (value,)
values = tuple(
self._convert_scalar(v, i) for (i, v) in filter(
- lambda tup: tup[1] not in NULLS, enumerate(value)
+ lambda iv: iv[1] not in NULLS, enumerate(value)
)
)
if len(values) == 0:
@@ -420,10 +476,12 @@ class Param(ReadOnly):
def _convert_scalar(self, value, index=None):
"""
- Implement in subclass.
+ Convert a single scalar value.
"""
- raise NotImplementedError(
- '%s.%s()' % (self.__class__.__name__, '_convert_scalar')
+ if type(value) is self.type:
+ return value
+ raise ConversionError(name=self.name, index=index,
+ error=ugettext(self.type_error),
)
def validate(self, value):
@@ -432,43 +490,268 @@ class Param(ReadOnly):
:param value: A proposed value for this parameter.
"""
+ # FIXME: this should be after 'if value is None:'
+ if self.query:
+ return
+ if value is None:
+ if self.required:
+ raise RequirementError(name=self.name)
+ return
+ if self.multivalue:
+ if type(value) is not tuple:
+ raise TypeError(
+ TYPE_ERROR % ('value', tuple, value, type(value))
+ )
+ if len(value) < 1:
+ raise ValueError('value: empty tuple must be converted to None')
+ for (i, v) in enumerate(value):
+ self._validate_scalar(v, i)
+ else:
+ self._validate_scalar(value)
+
+ def _validate_scalar(self, value, index=None):
+ if type(value) is not self.type:
+ if index is None:
+ name = 'value'
+ else:
+ name = 'value[%d]' % index
+ raise TypeError(
+ TYPE_ERROR % (name, self.type, value, type(value))
+ )
+ if index is not None and type(index) is not int:
+ raise TypeError(
+ TYPE_ERROR % ('index', int, index, type(index))
+ )
+ for rule in self.all_rules:
+ error = rule(ugettext, value)
+ if error is not None:
+ raise ValidationError(
+ name=self.name,
+ value=value,
+ index=index,
+ error=error,
+ rule=rule,
+ )
+
+ def get_default(self, **kw):
+ """
+ Return the static default or construct and return a dynamic default.
+
+ (In these examples, we will use the `Str` and `Bytes` classes, which
+ both subclass from `Param`.)
+
+ The *default* static default is ``None``. For example:
+
+ >>> s = Str('my_str')
+ >>> s.default is None
+ True
+ >>> s.get_default() is None
+ True
+
+ However, you can provide your own static default via the ``default``
+ keyword argument when you create your `Param` instance. For example:
+
+ >>> s = Str('my_str', default=u'My Static Default')
+ >>> s.default
+ u'My Static Default'
+ >>> s.get_default()
+ u'My Static Default'
+
+ If you need to generate a dynamic default from other supplied parameter
+ values, provide a callback via the ``default_from`` keyword argument.
+ This callback will be automatically wrapped in a `DefaultFrom` instance
+ if it isn't one already (see the `DefaultFrom` class for all the gory
+ details). For example:
+
+ >>> login = Str('login', default=u'my-static-login-default',
+ ... default_from=lambda first, last: (first[0] + last).lower(),
+ ... )
+ >>> isinstance(login.default_from, DefaultFrom)
+ True
+ >>> login.default_from.keys
+ ('first', 'last')
+
+ Then when all the keys needed by the `DefaultFrom` instance are present,
+ the dynamic default is constructed and returned. For example:
+
+ >>> kw = dict(last=u'Doe', first=u'John')
+ >>> login.get_default(**kw)
+ u'jdoe'
+
+ Or if any keys are missing, your *static* default is returned.
+ For example:
+
+ >>> kw = dict(first=u'John', department=u'Engineering')
+ >>> login.get_default(**kw)
+ u'my-static-login-default'
+
+ The second, less common way to construct a dynamic default is to provide
+ a callback via the ``create_default`` keyword argument. Unlike a
+ ``default_from`` callback, your ``create_default`` callback will not get
+ wrapped in any dispatcher. Instead, it will be called directly, which
+ means your callback must accept arbitrary keyword arguments, although
+ whether your callback utilises these values is up to your
+ implementation. For example:
+
+ >>> def make_csr(**kw):
+ ... print ' make_csr(%r)' % (kw,) # Note output below
+ ... return 'Certificate Signing Request'
+ ...
+ >>> csr = Bytes('csr', create_default=make_csr)
+
+ Your ``create_default`` callback will be called with whatever keyword
+ arguments are passed to `Param.get_default()`. For example:
+
+ >>> kw = dict(arbitrary='Keyword', arguments='Here')
+ >>> csr.get_default(**kw)
+ make_csr({'arguments': 'Here', 'arbitrary': 'Keyword'})
+ 'Certificate Signing Request'
+
+ And your ``create_default`` callback is called even if
+ `Param.get_default()` is called with *zero* keyword arguments.
+ For example:
+
+ >>> csr.get_default()
+ make_csr({})
+ 'Certificate Signing Request'
+
+ The ``create_default`` callback will most likely be used as a
+ pre-execute hook to perform some special client-side operation. For
+ example, the ``csr`` parameter above might make a call to
+ ``/usr/bin/openssl``. However, often a ``create_default`` callback
+ could also be implemented as a ``default_from`` callback. When this is
+ the case, a ``default_from`` callback should be used as they are more
+ structured and therefore less error-prone.
+
+ The ``default_from`` and ``create_default`` keyword arguments are
+ mutually exclusive. If you provide both, a ``ValueError`` will be
+ raised. For example:
+
+ >>> homedir = Str('home',
+ ... default_from=lambda login: '/home/%s' % login,
+ ... create_default=lambda **kw: '/lets/use/this',
+ ... )
+ Traceback (most recent call last):
+ ...
+ ValueError: Str('home'): cannot have both 'default_from' and 'create_default'
+ """
+ if self._get_default is not None:
+ default = self._get_default(**kw)
+ if default is not None:
+ try:
+ return self.convert(self.normalize(default))
+ except StandardError:
+ pass
+ return self.default
class Bool(Param):
"""
-
+ A parameter for boolean values (stored in the ``bool`` type).
"""
+ type = bool
+ type_error = _('must be True or False')
+
-class Int(Param):
+class Flag(Bool):
"""
+ A boolean parameter that always gets filled in with a default value.
+
+ This `Bool` subclass forces ``autofill=True`` in `Flag.__init__()`. If no
+ default is provided, it also fills in a default value of ``False``.
+ Lastly, unlike the `Bool` class, the default must be either ``True`` or
+ ``False`` and cannot be ``None``.
+
+ For example:
+
+ >>> flag = Flag('my_flag')
+ >>> (flag.autofill, flag.default)
+ (True, False)
+
+ To have a default value of ``True``, create your `Flag` intance with
+ ``default=True``. For example:
+
+ >>> flag = Flag('my_flag', default=True)
+ >>> (flag.autofill, flag.default)
+ (True, True)
+ Also note that creating a `Flag` instance with ``autofill=False`` will have
+ no effect. For example:
+
+ >>> flag = Flag('my_flag', autofill=False)
+ >>> flag.autofill
+ True
"""
+ def __init__(self, name, *rules, **kw):
+ kw['autofill'] = True
+ if 'default' not in kw:
+ kw['default'] = False
+ if type(kw['default']) is not bool:
+ default = kw['default']
+ raise TypeError(
+ TYPE_ERROR % ('default', bool, default, type(default))
+ )
+ super(Flag, self).__init__(name, *rules, **kw)
+
-class Float(Param):
+class Number(Param):
"""
+ Base class for the `Int` and `Float` parameters.
+ """
+
+ def _convert_scalar(self, value, index=None):
+ """
+ Convert a single scalar value.
+ """
+ if type(value) is self.type:
+ return value
+ if type(value) in (unicode, int, float):
+ try:
+ return self.type(value)
+ except ValueError:
+ pass
+ raise ConversionError(name=self.name, index=index,
+ error=ugettext(self.type_error),
+ )
+
+class Int(Number):
"""
+ A parameter for integer values (stored in the ``int`` type).
+ """
+
+ type = int
+ type_error = _('must be an integer')
-class Bytes(Param):
+class Float(Number):
"""
+ A parameter for floating-point values (stored in the ``float`` type).
+ """
+
+ type = float
+ type_error = _('must be a decimal number')
+
+class Data(Param):
"""
+ Base class for the `Bytes` and `Str` parameters.
- type = str
+ Previously `Str` was as subclass of `Bytes`. Now the common functionality
+ has been split into this base class so that ``isinstance(foo, Bytes)`` wont
+ be ``True`` when ``foo`` is actually an `Str` instance (which is confusing).
+ """
kwargs = Param.kwargs + (
('minlength', int, None),
('maxlength', int, None),
('length', int, None),
- ('pattern', str, None),
-
)
- def __init__(self, name, **kw):
- super(Bytes, self).__init__(name, **kw)
+ def __init__(self, name, *rules, **kw):
+ super(Data, self).__init__(name, *rules, **kw)
if not (
self.length is None or
@@ -500,92 +783,158 @@ class Bytes(Param):
self.nice, self.minlength)
)
- def _rule_minlength(self, _, name, value):
+
+class Bytes(Data):
+ """
+ A parameter for binary data (stored in the ``str`` type).
+
+ This class is named *Bytes* instead of *Str* so it's aligned with the
+ Python v3 ``(str, unicode) => (bytes, str)`` clean-up. See:
+
+ http://docs.python.org/3.0/whatsnew/3.0.html
+ """
+
+ type = str
+ type_error = _('must be binary data')
+
+ kwargs = Data.kwargs + (
+ ('pattern', str, None),
+ )
+
+ def _rule_minlength(self, _, value):
"""
Check minlength constraint.
"""
assert type(value) is str
if len(value) < self.minlength:
- return _('%(name)s must be at least %(minlength)d bytes') % dict(
- name=name,
+ return _('must be at least %(minlength)d bytes') % dict(
minlength=self.minlength,
)
- def _rule_maxlength(self, _, name, value):
+ def _rule_maxlength(self, _, value):
"""
Check maxlength constraint.
"""
assert type(value) is str
if len(value) > self.maxlength:
- return _('%(name)s can be at most %(maxlength)d bytes') % dict(
- name=name,
+ return _('can be at most %(maxlength)d bytes') % dict(
maxlength=self.maxlength,
)
- def _rule_length(self, _, name, value):
+ def _rule_length(self, _, value):
"""
Check length constraint.
"""
assert type(value) is str
if len(value) != self.length:
- return _('%(name)s must be exactly %(length)d bytes') % dict(
- name=name,
+ return _('must be exactly %(length)d bytes') % dict(
length=self.length,
)
-
-
-class Str(Bytes):
+class Str(Data):
"""
+ A parameter for Unicode text (stored in the ``unicode`` type).
+ This class is named *Str* instead of *Unicode* so it's aligned with the
+ Python v3 ``(str, unicode) => (bytes, str)`` clean-up. See:
+
+ http://docs.python.org/3.0/whatsnew/3.0.html
"""
type = unicode
+ type_error = _('must be Unicode text')
- kwargs = Bytes.kwargs[:-1] + (
+ kwargs = Data.kwargs + (
('pattern', unicode, None),
)
- 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):
+ """
+ Convert a single scalar value.
+ """
+ if type(value) is self.type:
+ return value
+ if type(value) in (int, float):
return self.type(value)
- raise TypeError(
- 'Can only implicitly convert int, float, or bool; got %r' % value
+ raise ConversionError(name=self.name, index=index,
+ error=ugettext(self.type_error),
)
- def _rule_minlength(self, _, name, value):
+ def _rule_minlength(self, _, value):
"""
Check minlength constraint.
"""
assert type(value) is unicode
if len(value) < self.minlength:
- return _('%(name)s must be at least %(minlength)d characters') % dict(
- name=name,
+ return _('must be at least %(minlength)d characters') % dict(
minlength=self.minlength,
)
- def _rule_maxlength(self, _, name, value):
+ def _rule_maxlength(self, _, value):
"""
Check maxlength constraint.
"""
assert type(value) is unicode
if len(value) > self.maxlength:
- return _('%(name)s can be at most %(maxlength)d characters') % dict(
- name=name,
+ return _('can be at most %(maxlength)d characters') % dict(
maxlength=self.maxlength,
)
- def _rule_length(self, _, name, value):
+ def _rule_length(self, _, value):
"""
Check length constraint.
"""
assert type(value) is unicode
if len(value) != self.length:
- return _('%(name)s must be exactly %(length)d characters') % dict(
- name=name,
+ return _('must be exactly %(length)d characters') % dict(
length=self.length,
)
+
+
+class Password(Str):
+ """
+ A parameter for passwords (stored in the ``unicode`` type).
+ """
+
+
+def create_param(spec):
+ """
+ Create an `Str` instance from the shorthand ``spec``.
+
+ This function allows you to create `Str` parameters (the most common) from
+ a convenient shorthand that defines the parameter name, whether it is
+ required, and whether it is multivalue. (For the definition of the
+ shorthand syntax, see the `parse_param_spec()` function.)
+
+ If ``spec`` is an ``str`` instance, it will be used to create a new `Str`
+ parameter, which will be returned. For example:
+
+ >>> s = create_param('hometown?')
+ >>> s
+ Str('hometown?')
+ >>> (s.name, s.required, s.multivalue)
+ ('hometown', False, False)
+
+ On the other hand, if ``spec`` is already a `Param` instance, it is
+ returned unchanged. For example:
+
+ >>> b = Bytes('cert')
+ >>> create_param(b) is b
+ True
+
+ As a plugin author, you will not call this function directly (which would
+ be no more convenient than simply creating the `Str` instance). Instead,
+ `frontend.Command` will call it for you when it evaluates the
+ ``takes_args`` and ``takes_options`` attributes, and `frontend.Object`
+ will call it for you when it evaluates the ``takes_params`` attribute.
+
+ :param spec: A spec string or a `Param` instance.
+ """
+ if isinstance(spec, Param):
+ return spec
+ if type(spec) is not str:
+ raise TypeError(
+ TYPE_ERROR % ('spec', (str, Param), spec, type(spec))
+ )
+ return Str(spec)
diff --git a/ipalib/plugins/f_automount.py b/ipalib/plugins/f_automount.py
index 4c392438..2365ce22 100644
--- a/ipalib/plugins/f_automount.py
+++ b/ipalib/plugins/f_automount.py
@@ -23,13 +23,9 @@ Frontend plugins for automount.
RFC 2707bis http://www.padl.com/~lukeh/rfc2307bis.txt
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
from ldap import explode_dn
+from ipalib import crud, errors
+from ipalib import api, Str, Flag, Object, Command
map_attributes = ['automountMapName', 'description', ]
key_attributes = ['description', 'automountKey', 'automountInformation']
@@ -57,12 +53,12 @@ def make_automount_dn(mapname):
api.env.basedn,
)
-class automount(frontend.Object):
+class automount(Object):
"""
Automount object.
"""
takes_params = (
- Param('automountmapname',
+ Str('automountmapname',
cli_name='mapname',
primary_key=True,
doc='A group of related automount objects',
@@ -73,8 +69,9 @@ api.register(automount)
class automount_addmap(crud.Add):
'Add a new automount map.'
+
takes_options = (
- Param('description?',
+ Str('description?',
doc='A description of the automount map'),
)
@@ -96,6 +93,7 @@ class automount_addmap(crud.Add):
kw['objectClass'] = ['automountMap']
return ldap.create(**kw)
+
def output_for_cli(self, textui, result, map, **options):
"""
Output result of this command to command line interface.
@@ -108,13 +106,13 @@ api.register(automount_addmap)
class automount_addkey(crud.Add):
'Add a new automount key.'
takes_options = (
- Param('automountkey',
+ Str('automountkey',
cli_name='key',
doc='An entry in an automount map'),
- Param('automountinformation',
+ Str('automountinformation',
cli_name='info',
doc='Mount information for this key'),
- Param('description?',
+ Str('description?',
doc='A description of the mount'),
)
@@ -138,6 +136,7 @@ class automount_addkey(crud.Add):
kw['objectClass'] = ['automount']
return ldap.create(**kw)
+
def output_for_cli(self, textui, result, *args, **options):
"""
Output result of this command to command line interface.
@@ -177,7 +176,7 @@ api.register(automount_delmap)
class automount_delkey(crud.Del):
'Delete an automount key.'
takes_options = (
- Param('automountkey',
+ Str('automountkey',
cli_name='key',
doc='The automount key to remove'),
)
@@ -213,7 +212,7 @@ api.register(automount_delkey)
class automount_modmap(crud.Mod):
'Edit an existing automount map.'
takes_options = (
- Param('description?',
+ Str('description?',
doc='A description of the automount map'),
)
def execute(self, mapname, **kw):
@@ -246,13 +245,13 @@ api.register(automount_modmap)
class automount_modkey(crud.Mod):
'Edit an existing automount key.'
takes_options = (
- Param('automountkey',
+ Str('automountkey',
cli_name='key',
doc='An entry in an automount map'),
- Param('automountinformation?',
+ Str('automountinformation?',
cli_name='info',
doc='Mount information for this key'),
- Param('description?',
+ Str('description?',
doc='A description of the automount map'),
)
def execute(self, mapname, **kw):
@@ -293,7 +292,7 @@ api.register(automount_modkey)
class automount_findmap(crud.Find):
'Search automount maps.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all attributes'),
+ Flag('all', doc='Retrieve all attributes'),
)
def execute(self, term, **kw):
ldap = self.api.Backend.ldap
@@ -331,10 +330,10 @@ api.register(automount_findmap)
class automount_findkey(crud.Find):
'Search automount keys.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all attributes'),
+ Flag('all?', doc='Retrieve all attributes'),
)
def get_args(self):
- return (Param('automountkey',
+ return (Str('automountkey',
cli_name='key',
doc='An entry in an automount map'),)
def execute(self, term, **kw):
@@ -372,7 +371,7 @@ api.register(automount_findkey)
class automount_showmap(crud.Get):
'Examine an existing automount map.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all attributes'),
+ Flag('all?', doc='Retrieve all attributes'),
)
def execute(self, mapname, **kw):
"""
@@ -400,10 +399,10 @@ api.register(automount_showmap)
class automount_showkey(crud.Get):
'Examine an existing automount key.'
takes_options = (
- Param('automountkey',
+ Str('automountkey',
cli_name='key',
doc='The automount key to display'),
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all attributes'),
+ Flag('all?', doc='Retrieve all attributes'),
)
def execute(self, mapname, **kw):
"""
@@ -446,10 +445,10 @@ class automount_showkey(crud.Get):
api.register(automount_showkey)
-class automount_getkeys(frontend.Command):
+class automount_getkeys(Command):
'Retrieve all keys for an automount map.'
takes_args = (
- Param('automountmapname',
+ Str('automountmapname',
cli_name='mapname',
primary_key=True,
doc='A group of related automount objects',
@@ -478,10 +477,10 @@ class automount_getkeys(frontend.Command):
api.register(automount_getkeys)
-class automount_getmaps(frontend.Command):
+class automount_getmaps(Command):
'Retrieve all automount maps'
takes_args = (
- Param('automountmapname?',
+ Str('automountmapname?',
cli_name='mapname',
primary_key=True,
doc='A group of related automount objects',
@@ -510,17 +509,23 @@ class automount_getmaps(frontend.Command):
api.register(automount_getmaps)
class automount_addindirectmap(crud.Add):
- 'Add a new automap indirect mount point.'
+ """
+ Add a new automap indirect mount point.
+ """
+
takes_options = (
- Param('parentmap?',
+ Str('parentmap?',
cli_name='parentmap',
- default='auto.master',
- doc='The parent map to connect this to. Default: auto.master'),
- Param('automountkey',
+ default=u'auto.master',
+ doc='The parent map to connect this to.',
+ ),
+ Str('automountkey',
cli_name='key',
- doc='An entry in an automount map'),
- Param('description?',
- doc='A description of the automount map'),
+ doc='An entry in an automount map',
+ ),
+ Str('description?',
+ doc='A description of the automount map',
+ ),
)
def execute(self, mapname, **kw):
@@ -556,4 +561,3 @@ class automount_addindirectmap(crud.Add):
textui.print_plain("Indirect automount map %s added" % map)
api.register(automount_addindirectmap)
-
diff --git a/ipalib/plugins/f_group.py b/ipalib/plugins/f_group.py
index 803e5d00..740b32f8 100644
--- a/ipalib/plugins/f_group.py
+++ b/ipalib/plugins/f_group.py
@@ -21,12 +21,9 @@
Frontend plugins for group (Identity).
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str, Int # Parameter types
def get_members(members):
@@ -42,24 +39,23 @@ def get_members(members):
return members
-class group(frontend.Object):
+class group(Object):
"""
Group object.
"""
takes_params = (
- Param('description',
+ Str('description',
doc='A description of this group',
),
- Param('gidnumber?',
+ Int('gidnumber?',
cli_name='gid',
- type=ipa_types.Int(),
doc='The gid to use for this group. If not included one is automatically set.',
),
- Param('cn',
+ Str('cn',
cli_name='name',
primary_key=True,
- normalize=lambda value: value.lower(),
- )
+ normalizer=lambda value: value.lower(),
+ ),
)
api.register(group)
@@ -256,14 +252,14 @@ class group_show(crud.Get):
api.register(group_show)
-class group_add_member(frontend.Command):
+class group_add_member(Command):
'Add a member to a group.'
takes_args = (
- Param('group', primary_key=True),
+ Str('group', primary_key=True),
)
takes_options = (
- Param('users?', doc='comma-separated list of users to add'),
- Param('groups?', doc='comma-separated list of groups to add'),
+ Str('users?', doc='comma-separated list of users to add'),
+ Str('groups?', doc='comma-separated list of groups to add'),
)
def execute(self, cn, **kw):
"""
@@ -323,14 +319,14 @@ class group_add_member(frontend.Command):
api.register(group_add_member)
-class group_remove_member(frontend.Command):
+class group_remove_member(Command):
'Remove a member from a group.'
takes_args = (
- Param('group', primary_key=True),
+ Str('group', primary_key=True),
)
takes_options = (
- Param('users?', doc='comma-separated list of users to remove'),
- Param('groups?', doc='comma-separated list of groups to remove'),
+ Str('users?', doc='comma-separated list of users to remove'),
+ Str('groups?', doc='comma-separated list of groups to remove'),
)
def execute(self, cn, **kw):
"""
diff --git a/ipalib/plugins/f_host.py b/ipalib/plugins/f_host.py
index 7903ff90..3fcda77c 100644
--- a/ipalib/plugins/f_host.py
+++ b/ipalib/plugins/f_host.py
@@ -21,13 +21,9 @@
Frontend plugins for host/machine Identity.
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib import util
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
+from ipalib import api, crud, errors
+from ipalib import Object # Plugin base class
+from ipalib import Str, Flag # Parameter types
def get_host(hostname):
@@ -57,37 +53,36 @@ def validate_host(cn):
default_attributes = ['cn','description','localityname','nshostlocation','nshardwareplatform','nsosversion']
-class host(frontend.Object):
+class host(Object):
"""
Host object.
"""
takes_params = (
- Param('cn',
+ Str('cn', validate_host,
cli_name='hostname',
primary_key=True,
- normalize=lambda value: value.lower(),
- rules=(validate_host,)
+ normalizer=lambda value: value.lower(),
),
- Param('description?',
+ Str('description?',
doc='Description of the host',
),
- Param('localityname?',
+ Str('localityname?',
cli_name='locality',
doc='Locality of this host (Baltimore, MD)',
),
- Param('nshostlocation?',
+ Str('nshostlocation?',
cli_name='location',
doc='Location of this host (e.g. Lab 2)',
),
- Param('nshardwareplatform?',
+ Str('nshardwareplatform?',
cli_name='platform',
doc='Hardware platform of this host (e.g. Lenovo T61)',
),
- Param('nsosversion?',
+ Str('nsosversion?',
cli_name='os',
doc='Operating System and version on this host (e.g. Fedora 9)',
),
- Param('userpassword?',
+ Str('userpassword?',
cli_name='password',
doc='Set a password to be used in bulk enrollment',
),
@@ -211,14 +206,18 @@ api.register(host_mod)
class host_find(crud.Find):
'Search the hosts.'
+
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all attributes'),
+ Flag('all', doc='Retrieve all attributes'),
)
- def get_args(self):
- """
- Override Find.get_args() so we can exclude the validation rules
- """
- yield self.obj.primary_key.__clone__(rules=tuple())
+
+ # FIXME: This should no longer be needed with the Param.query kwarg.
+# def get_args(self):
+# """
+# Override Find.get_args() so we can exclude the validation rules
+# """
+# yield self.obj.primary_key.__clone__(rules=tuple())
+
def execute(self, term, **kw):
ldap = self.api.Backend.ldap
@@ -258,7 +257,7 @@ api.register(host_find)
class host_show(crud.Get):
'Examine an existing host.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Display all host attributes'),
+ Flag('all', doc='Display all host attributes'),
)
def execute(self, hostname, **kw):
"""
diff --git a/ipalib/plugins/f_hostgroup.py b/ipalib/plugins/f_hostgroup.py
index 3e14b09a..c365c918 100644
--- a/ipalib/plugins/f_hostgroup.py
+++ b/ipalib/plugins/f_hostgroup.py
@@ -21,12 +21,10 @@
Frontend plugins for groups of hosts
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str # Parameter types
+
hostgroup_filter = "groupofnames)(!(objectclass=posixGroup)"
@@ -43,18 +41,18 @@ def get_members(members):
return members
-class hostgroup(frontend.Object):
+class hostgroup(Object):
"""
Host Group object.
"""
takes_params = (
- Param('description',
+ Str('description',
doc='A description of this group',
),
- Param('cn',
+ Str('cn',
cli_name='name',
primary_key=True,
- normalize=lambda value: value.lower(),
+ normalizer=lambda value: value.lower(),
)
)
api.register(hostgroup)
@@ -220,14 +218,14 @@ class hostgroup_show(crud.Get):
api.register(hostgroup_show)
-class hostgroup_add_member(frontend.Command):
+class hostgroup_add_member(Command):
'Add a member to a group.'
takes_args = (
- Param('group', primary_key=True),
+ Str('group', primary_key=True),
)
takes_options = (
- Param('groups?', doc='comma-separated list of host groups to add'),
- Param('hosts?', doc='comma-separated list of hosts to add'),
+ Str('groups?', doc='comma-separated list of host groups to add'),
+ Str('hosts?', doc='comma-separated list of hosts to add'),
)
def execute(self, cn, **kw):
"""
@@ -288,14 +286,14 @@ class hostgroup_add_member(frontend.Command):
api.register(hostgroup_add_member)
-class hostgroup_remove_member(frontend.Command):
+class hostgroup_remove_member(Command):
'Remove a member from a group.'
takes_args = (
- Param('group', primary_key=True),
+ Str('group', primary_key=True),
)
takes_options = (
- Param('hosts?', doc='comma-separated list of hosts to add'),
- Param('groups?', doc='comma-separated list of groups to remove'),
+ Str('hosts?', doc='comma-separated list of hosts to add'),
+ Str('groups?', doc='comma-separated list of groups to remove'),
)
def execute(self, cn, **kw):
"""
diff --git a/ipalib/plugins/f_passwd.py b/ipalib/plugins/f_passwd.py
index 1e0dfc1c..ea78c4c1 100644
--- a/ipalib/plugins/f_passwd.py
+++ b/ipalib/plugins/f_passwd.py
@@ -21,23 +21,21 @@
Frontend plugins for password changes.
"""
-from ipalib import frontend
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
-from ipalib import util
+from ipalib import api, errors, util
+from ipalib import Command # Plugin base classes
+from ipalib import Str, Password # Parameter types
-class passwd(frontend.Command):
+
+class passwd(Command):
'Edit existing password policy.'
takes_args = (
- Param('principal',
+ Str('principal',
cli_name='user',
primary_key=True,
default_from=util.get_current_principal,
),
- Param('password', flags=['password']),
+ Password('password'),
)
def execute(self, principal, password):
diff --git a/ipalib/plugins/f_pwpolicy.py b/ipalib/plugins/f_pwpolicy.py
index 87a7d8fa..d914ce72 100644
--- a/ipalib/plugins/f_pwpolicy.py
+++ b/ipalib/plugins/f_pwpolicy.py
@@ -21,40 +21,32 @@
Frontend plugins for password policy.
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
+from ipalib import Command # Plugin base classes
+from ipalib import Int # Parameter types
-class pwpolicy_mod(frontend.Command):
+class pwpolicy_mod(Command):
'Edit existing password policy.'
takes_options = (
- Param('krbmaxpwdlife?',
+ Int('krbmaxpwdlife?',
cli_name='maxlife',
- type=ipa_types.Int(),
doc='Max. Password Lifetime (days)'
),
- Param('krbminpwdlife?',
+ Int('krbminpwdlife?',
cli_name='minlife',
- type=ipa_types.Int(),
doc='Min. Password Lifetime (hours)'
),
- Param('krbpwdhistorylength?',
+ Int('krbpwdhistorylength?',
cli_name='history',
- type=ipa_types.Int(),
doc='Password History Size'
),
- Param('krbpwdmindiffchars?',
+ Int('krbpwdmindiffchars?',
cli_name='minclasses',
- type=ipa_types.Int(),
doc='Min. Number of Character Classes'
),
- Param('krbpwdminlength?',
+ Int('krbpwdminlength?',
cli_name='minlength',
- type=ipa_types.Int(),
doc='Min. Length of Password'
),
)
@@ -94,7 +86,7 @@ class pwpolicy_mod(frontend.Command):
api.register(pwpolicy_mod)
-class pwpolicy_show(frontend.Command):
+class pwpolicy_show(Command):
'Retrieve current password policy'
def execute(self, *args, **kw):
"""
diff --git a/ipalib/plugins/f_ra.py b/ipalib/plugins/f_ra.py
index 724cbf5e..7ac84e65 100644
--- a/ipalib/plugins/f_ra.py
+++ b/ipalib/plugins/f_ra.py
@@ -22,8 +22,7 @@
Frontend plugins for IPA-RA PKI operations.
"""
-from ipalib import api, Command, Param
-from ipalib import cli
+from ipalib import api, Command, Str, Int
class request_certificate(Command):
@@ -31,7 +30,7 @@ class request_certificate(Command):
takes_args = ['csr']
- takes_options = [Param('request_type?', default='pkcs10')]
+ takes_options = [Str('request_type?', default=u'pkcs10')]
def execute(self, csr, **options):
return self.Backend.ra.request_certificate(csr, **options)
@@ -85,7 +84,8 @@ class revoke_certificate(Command):
takes_args = ['serial_number']
- takes_options = [Param('revocation_reason?', default=0)]
+ # FIXME: The default is 0. Is this really an Int param?
+ takes_options = [Int('revocation_reason?', default=0)]
def execute(self, serial_number, **options):
@@ -115,5 +115,3 @@ class take_certificate_off_hold(Command):
textui.print_plain('Failed to take a revoked certificate off hold.')
api.register(take_certificate_off_hold)
-
-
diff --git a/ipalib/plugins/f_service.py b/ipalib/plugins/f_service.py
index a353d52e..06d6a5d0 100644
--- a/ipalib/plugins/f_service.py
+++ b/ipalib/plugins/f_service.py
@@ -22,27 +22,30 @@
Frontend plugins for service (Identity).
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
-
-class service(frontend.Object):
+from ipalib import api, crud, errors
+from ipalib import Object # Plugin base classes
+from ipalib import Str, Flag # Parameter types
+
+
+class service(Object):
"""
Service object.
"""
takes_params = (
- Param('principal', primary_key=True),
+ Str('principal', primary_key=True),
)
api.register(service)
class service_add(crud.Add):
- 'Add a new service.'
+ """
+ Add a new service.
+ """
+
takes_options = (
- Param('force?', type=ipa_types.Bool(), default=False, doc='Force a service principal name'),
+ Flag('force',
+ doc='Force a service principal name',
+ ),
)
def execute(self, principal, **kw):
"""
diff --git a/ipalib/plugins/f_user.py b/ipalib/plugins/f_user.py
index 04d7c930..506ad14d 100644
--- a/ipalib/plugins/f_user.py
+++ b/ipalib/plugins/f_user.py
@@ -21,12 +21,9 @@
Frontend plugins for user (Identity).
"""
-from ipalib import frontend
-from ipalib import crud
-from ipalib.frontend import Param
-from ipalib import api
-from ipalib import errors
-from ipalib import ipa_types
+from ipalib import api, crud, errors
+from ipalib import Object, Command # Plugin base classes
+from ipalib import Str, Password, Flag, Int # Parameter types
def display_user(user):
@@ -48,62 +45,62 @@ def display_user(user):
default_attributes = ['uid','givenname','sn','homeDirectory','loginshell']
-class user(frontend.Object):
+class user(Object):
"""
User object.
"""
+
takes_params = (
- Param('givenname',
+ Str('givenname',
cli_name='first',
- doc='User\'s first name',
+ doc="User's first name",
),
- Param('sn',
+ Str('sn',
cli_name='last',
- doc='User\'s last name',
+ doc="User's last name",
),
- Param('uid',
+ Str('uid',
cli_name='user',
primary_key=True,
default_from=lambda givenname, sn: givenname[0] + sn,
- normalize=lambda value: value.lower(),
+ normalizer=lambda value: value.lower(),
),
- Param('gecos?',
+ Str('gecos?',
doc='GECOS field',
default_from=lambda uid: uid,
),
- Param('homedirectory?',
+ Str('homedirectory?',
cli_name='home',
- doc='User\'s home directory',
+ doc="User's home directory",
default_from=lambda uid: '/home/%s' % uid,
),
- Param('loginshell?',
+ Str('loginshell?',
cli_name='shell',
default=u'/bin/sh',
- doc='User\'s Login shell',
+ doc="User's Login shell",
),
- Param('krbprincipalname?', cli_name='principal',
- doc='User\'s Kerberos Principal name',
+ Str('krbprincipalname?',
+ cli_name='principal',
+ doc="User's Kerberos Principal name",
default_from=lambda uid: '%s@%s' % (uid, api.env.realm),
),
- Param('mailaddress?',
- cli_name='mail',
- doc='User\'s e-mail address',
+ Str('mailaddress?',
+ cli_name='email',
+ doc="User's e-mail address",
),
- Param('userpassword?',
+ Password('userpassword?',
cli_name='password',
doc="Set user's password",
- flags=['password'],
),
- Param('groups?',
+ Str('groups?',
doc='Add account to one or more groups (comma-separated)',
),
- Param('uidnumber?',
+ Int('uidnumber?',
cli_name='uid',
- type=ipa_types.Int(),
doc='The uid to use for this user. If not included one is automatically set.',
),
-
)
+
api.register(user)
@@ -254,7 +251,7 @@ api.register(user_mod)
class user_find(crud.Find):
'Search the users.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all user attributes'),
+ Flag('all', doc='Retrieve all user attributes'),
)
def execute(self, term, **kw):
ldap = self.api.Backend.ldap
@@ -304,7 +301,7 @@ api.register(user_find)
class user_show(crud.Get):
'Examine an existing user.'
takes_options = (
- Param('all?', type=ipa_types.Bool(), doc='Retrieve all user attributes'),
+ Flag('all', doc='Retrieve all user attributes'),
)
def execute(self, uid, **kw):
"""
@@ -332,11 +329,11 @@ class user_show(crud.Get):
api.register(user_show)
-class user_lock(frontend.Command):
+class user_lock(Command):
'Lock a user account.'
takes_args = (
- Param('uid', primary_key=True),
+ Str('uid', primary_key=True),
)
def execute(self, uid, **kw):
@@ -351,11 +348,11 @@ class user_lock(frontend.Command):
api.register(user_lock)
-class user_unlock(frontend.Command):
+class user_unlock(Command):
'Unlock a user account.'
takes_args = (
- Param('uid', primary_key=True),
+ Str('uid', primary_key=True),
)
def execute(self, uid, **kw):
diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py
index 94e586fe..071a70fd 100644
--- a/tests/test_ipalib/test_frontend.py
+++ b/tests/test_ipalib/test_frontend.py
@@ -23,7 +23,9 @@ Test the `ipalib.frontend` module.
from tests.util import raises, getitem, no_set, no_del, read_only
from tests.util import check_TypeError, ClassChecker, create_test_api
-from ipalib import frontend, backend, plugable, errors, ipa_types, config
+from tests.util import assert_equal
+from ipalib.constants import TYPE_ERROR
+from ipalib import frontend, backend, plugable, errors2, errors, parameters, config
def test_RULE_FLAG():
@@ -70,420 +72,6 @@ def test_is_rule():
assert not is_rule(call(None))
-class test_DefaultFrom(ClassChecker):
- """
- Test the `ipalib.frontend.DefaultFrom` class.
- """
- _cls = frontend.DefaultFrom
-
- def test_class(self):
- """
- Test the `ipalib.frontend.DefaultFrom` class.
- """
- assert self.cls.__bases__ == (plugable.ReadOnly,)
-
- def test_init(self):
- """
- Test the `ipalib.frontend.DefaultFrom.__init__` method.
- """
- def callback(*args):
- return args
- keys = ('givenname', 'sn')
- o = self.cls(callback, *keys)
- assert read_only(o, 'callback') is callback
- assert read_only(o, 'keys') == keys
- lam = lambda first, last: first[0] + last
- o = self.cls(lam)
- assert read_only(o, 'keys') == ('first', 'last')
-
- def test_call(self):
- """
- Test the `ipalib.frontend.DefaultFrom.__call__` method.
- """
- def callback(givenname, sn):
- return givenname[0] + sn[0]
- keys = ('givenname', 'sn')
- o = self.cls(callback, *keys)
- kw = dict(
- givenname='John',
- sn='Public',
- hello='world',
- )
- assert o(**kw) == 'JP'
- assert o() is None
- for key in ('givenname', 'sn'):
- kw_copy = dict(kw)
- del kw_copy[key]
- assert o(**kw_copy) is None
-
- # Test using implied keys:
- o = self.cls(lambda first, last: first[0] + last)
- assert o(first='john', last='doe') == 'jdoe'
- assert o(first='', last='doe') is None
- assert o(one='john', two='doe') is None
-
- # Test that co_varnames slice is used:
- def callback2(first, last):
- letter = first[0]
- return letter + last
- o = self.cls(callback2)
- assert o.keys == ('first', 'last')
- assert o(first='john', last='doe') == 'jdoe'
-
-
-def test_parse_param_spec():
- """
- Test the `ipalib.frontend.parse_param_spec` function.
- """
- f = frontend.parse_param_spec
-
- assert f('name') == ('name', dict(required=True, multivalue=False))
- 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))
-
-
-class test_Param(ClassChecker):
- """
- Test the `ipalib.frontend.Param` class.
- """
- _cls = frontend.Param
-
- def test_class(self):
- """
- Test the `ipalib.frontend.Param` class.
- """
- assert self.cls.__bases__ == (plugable.ReadOnly,)
-
- def test_init(self):
- """
- Test the `ipalib.frontend.Param.__init__` method.
- """
- name = 'sn'
- o = self.cls(name)
- assert o.__islocked__() is True
-
- # Test default values
- assert read_only(o, 'name') is name
- assert read_only(o, 'cli_name') is name
- assert isinstance(read_only(o, 'type'), ipa_types.Unicode)
- assert read_only(o, 'doc') == ''
- assert read_only(o, 'required') is True
- assert read_only(o, 'multivalue') is False
- assert read_only(o, 'default') is None
- assert read_only(o, 'default_from') is None
- assert read_only(o, 'flags') == frozenset()
- assert read_only(o, 'rules') == tuple()
- assert len(read_only(o, 'all_rules')) == 1
- assert read_only(o, 'primary_key') is False
-
- # Test all kw args:
- t = ipa_types.Int()
- assert self.cls(name, cli_name='last').cli_name == 'last'
- assert self.cls(name, type=t).type is t
- assert self.cls(name, doc='the doc').doc == 'the doc'
- assert self.cls(name, required=False).required is False
- assert self.cls(name, multivalue=True).multivalue is True
- assert self.cls(name, default=u'Hello').default == u'Hello'
- df = frontend.DefaultFrom(lambda f, l: f + l,
- 'first', 'last',
- )
- lam = lambda first, last: first + last
- for cb in (df, lam):
- o = self.cls(name, default_from=cb)
- assert type(o.default_from) is frontend.DefaultFrom
- assert o.default_from.keys == ('first', 'last')
- assert o.default_from.callback('butt', 'erfly') == 'butterfly'
- assert self.cls(name, flags=('one', 'two', 'three')).flags == \
- frozenset(['one', 'two', 'three'])
- rules = (lambda whatever: 'Not okay!',)
- o = self.cls(name, rules=rules)
- assert o.rules is rules
- assert o.all_rules[1:] == rules
- assert self.cls(name, primary_key=True).primary_key is True
-
- # Test default type_:
- o = self.cls(name)
- assert isinstance(o.type, ipa_types.Unicode)
-
- # Test param spec parsing:
- o = self.cls('name?')
- assert o.name == 'name'
- assert o.required is False
- assert o.multivalue is False
-
- o = self.cls('name*')
- assert o.name == 'name'
- assert o.required is False
- assert o.multivalue is True
-
- o = self.cls('name+')
- assert o.name == 'name'
- assert o.required is True
- assert o.multivalue is True
-
- e = raises(TypeError, self.cls, name, whatever=True, another=False)
- assert str(e) == \
- 'Param.__init__() takes no such kwargs: another, whatever'
-
- def test_ispassword(self):
- """
- Test the `ipalib.frontend.Param.ispassword` method.
- """
- name = 'userpassword'
- okay = 'password'
- nope = ['', 'pass', 'word', 'passwd']
- for flag in nope:
- o = self.cls(name, flags=[flag])
- assert o.ispassword() is False
- o = self.cls(name, flags=[flag, okay])
- assert o.ispassword() is True
- assert self.cls(name).ispassword() is False
- assert self.cls(name, flags=[okay]).ispassword() is True
- assert self.cls(name, flags=[okay]+nope).ispassword() is True
-
- def test_clone(self):
- """
- Test the `ipalib.frontend.Param.__clone__` method.
- """
- def compare(o, kw):
- for (k, v) in kw.iteritems():
- assert getattr(o, k) == v, (k, v, getattr(o, k))
- default = dict(
- required=False,
- multivalue=False,
- default=None,
- default_from=None,
- rules=tuple(),
- )
- name = 'hair_color?'
- type_ = ipa_types.Int()
- o = self.cls(name, type=type_)
- compare(o, default)
-
- override = dict(multivalue=True, default=42)
- d = dict(default)
- d.update(override)
- clone = o.__clone__(**override)
- assert clone.name == 'hair_color'
- assert clone.type is o.type
- compare(clone, d)
-
- def test_convert(self):
- """
- Test the `ipalib.frontend.Param.convert` method.
- """
- name = 'some_number'
- type_ = ipa_types.Int()
- okay = (7, 7L, 7.0, ' 7 ')
- fail = ('7.0', '7L', 'whatever', object)
- none = (None, '', u'', tuple(), [])
-
- # Scenario 1: multivalue=False
- o = self.cls(name, type=type_)
- for n in none:
- assert o.convert(n) is None
- for value in okay:
- new = o.convert(value)
- assert new == 7
- assert type(new) is int
- for value in fail:
- e = raises(errors.ConversionError, o.convert, value)
- assert e.name is name
- assert e.value is value
- assert e.error is type_.conversion_error
- assert e.index is None
-
- # Scenario 2: multivalue=True
- o = self.cls(name, type=type_, multivalue=True)
- for n in none:
- assert o.convert(n) is None
- for value in okay:
- assert o.convert((value,)) == (7,)
- assert o.convert([value]) == (7,)
- assert o.convert(okay) == tuple(int(v) for v in okay)
- cnt = 5
- for value in fail:
- for i in xrange(cnt):
- others = list(7 for x in xrange(cnt))
- others[i] = value
- for v in [tuple(others), list(others)]:
- e = raises(errors.ConversionError, o.convert, v)
- assert e.name is name
- assert e.value is value
- assert e.error is type_.conversion_error
- assert e.index == i
-
- def test_normalize(self):
- """
- Test the `ipalib.frontend.Param.normalize` method.
- """
- name = 'sn'
- callback = lambda value: value.lower()
- values = (None, u'Hello', (u'Hello',), 'hello', ['hello'])
- none = (None, '', u'', tuple(), [])
-
- # Scenario 1: multivalue=False, normalize=None
- o = self.cls(name)
- for v in values:
- # When normalize=None, value is returned, no type checking:
- assert o.normalize(v) is v
-
- # Scenario 2: multivalue=False, normalize=callback
- o = self.cls(name, normalize=callback)
- for v in (u'Hello', u'hello', 'Hello'): # Okay
- assert o.normalize(v) == 'hello'
- for v in [None, 42, (u'Hello',)]: # Not basestring
- assert o.normalize(v) is v
- for n in none:
- assert o.normalize(n) is None
-
- # Scenario 3: multivalue=True, normalize=None
- o = self.cls(name, multivalue=True)
- for v in values:
- # When normalize=None, value is returned, no type checking:
- assert o.normalize(v) is v
-
- # Scenario 4: multivalue=True, normalize=callback
- o = self.cls(name, multivalue=True, normalize=callback)
- assert o.normalize([]) is None
- assert o.normalize(tuple()) is None
- for value in [(u'Hello',), (u'hello',), 'Hello', ['Hello']]: # Okay
- assert o.normalize(value) == (u'hello',)
- fail = 42 # Not basestring
- for v in [[fail], (u'hello', fail)]: # Non basestring member
- assert o.normalize(v) == tuple(v)
- for n in none:
- assert o.normalize(n) is None
-
- def test_validate(self):
- """
- Test the `ipalib.frontend.Param.validate` method.
- """
- name = 'sn'
- type_ = ipa_types.Unicode()
- def case_rule(value):
- if not value.islower():
- return 'Must be lower case'
- my_rules = (case_rule,)
- okay = u'whatever'
- fail_case = u'Whatever'
- fail_type = 'whatever'
-
- # Scenario 1: multivalue=False
- o = self.cls(name, type=type_, rules=my_rules)
- assert o.rules == my_rules
- assert o.all_rules == (type_.validate, case_rule)
- o.validate(okay)
- e = raises(errors.RuleError, o.validate, fail_case)
- assert e.name is name
- assert e.value is fail_case
- assert e.error == 'Must be lower case'
- assert e.rule is case_rule
- assert e.index is None
- check_TypeError(fail_type, unicode, 'value', o.validate, fail_type)
-
- ## Scenario 2: multivalue=True
- o = self.cls(name, type=type_, multivalue=True, rules=my_rules)
- o.validate((okay,))
- cnt = 5
- for i in xrange(cnt):
- others = list(okay for x in xrange(cnt))
- others[i] = fail_case
- value = tuple(others)
- e = raises(errors.RuleError, o.validate, value)
- assert e.name is name
- assert e.value is fail_case
- assert e.error == 'Must be lower case'
- assert e.rule is case_rule
- assert e.index == i
- for not_tuple in (okay, [okay]):
- check_TypeError(not_tuple, tuple, 'value', o.validate, not_tuple)
- for has_str in [(fail_type,), (okay, fail_type)]:
- check_TypeError(fail_type, unicode, 'value', o.validate, has_str)
-
- def test_get_default(self):
- """
- Test the `ipalib.frontend.Param.get_default` method.
- """
- name = 'greeting'
- default = u'Hello, world!'
- default_from = frontend.DefaultFrom(
- lambda first, last: u'Hello, %s %s!' % (first, last),
- 'first', 'last'
- )
-
- # Scenario 1: multivalue=False
- o = self.cls(name,
- default=default,
- default_from=default_from,
- )
- assert o.default is default
- assert o.default_from is default_from
- assert o.get_default() == default
- assert o.get_default(first='John', last='Doe') == 'Hello, John Doe!'
-
- # Scenario 2: multivalue=True
- default = (default,)
- o = self.cls(name,
- default=default,
- default_from=default_from,
- multivalue=True,
- )
- assert o.default is default
- assert o.default_from is default_from
- assert o.get_default() == default
- assert o.get_default(first='John', last='Doe') == ('Hello, John Doe!',)
-
- def test_get_value(self):
- """
- Test the `ipalib.frontend.Param.get_values` method.
- """
- name = 'status'
- values = (u'Active', u'Inactive')
- o = self.cls(name, type=ipa_types.Unicode())
- assert o.get_values() == tuple()
- o = self.cls(name, type=ipa_types.Enum(*values))
- assert o.get_values() == values
-
- def test_repr(self):
- """
- Test the `ipalib.frontend.Param.__repr__` method.
- """
- for name in ['name', 'name?', 'name*', 'name+']:
- o = self.cls(name)
- assert repr(o) == 'Param(%r)' % name
- o = self.cls('name', required=False)
- assert repr(o) == "Param('name', required=False)"
- o = self.cls('name', multivalue=True)
- assert repr(o) == "Param('name', multivalue=True)"
-
-
-def test_create_param():
- """
- Test the `ipalib.frontend.create_param` function.
- """
- f = frontend.create_param
- for name in ['arg', 'arg?', 'arg*', 'arg+']:
- o = f(name)
- assert type(o) is frontend.Param
- assert type(o.type) is ipa_types.Unicode
- assert o.name == 'arg'
- assert f(o) is o
- o = f('arg')
- assert o.required is True
- assert o.multivalue is False
- o = f('arg?')
- assert o.required is False
- assert o.multivalue is False
- o = f('arg*')
- assert o.required is False
- assert o.multivalue is True
- o = f('arg+')
- assert o.required is True
- assert o.multivalue is True
-
-
class test_Command(ClassChecker):
"""
Test the `ipalib.frontend.Command` class.
@@ -499,28 +87,25 @@ class test_Command(ClassChecker):
def __init__(self, name):
self.name = name
- def __call__(self, value):
+ def __call__(self, _, value):
if value != self.name:
- return 'must equal %s' % self.name
+ return _('must equal %r') % self.name
- default_from = frontend.DefaultFrom(
+ default_from = parameters.DefaultFrom(
lambda arg: arg,
'default_from'
)
- normalize = lambda value: value.lower()
+ normalizer = lambda value: value.lower()
class example(self.cls):
takes_options = (
- frontend.Param('option0',
- normalize=normalize,
+ parameters.Str('option0', Rule('option0'),
+ normalizer=normalizer,
default_from=default_from,
- rules=(Rule('option0'),)
),
- frontend.Param('option1',
- normalize=normalize,
+ parameters.Str('option1', Rule('option1'),
+ normalizer=normalizer,
default_from=default_from,
- rules=(Rule('option1'),),
- required=True,
),
)
return example
@@ -577,8 +162,8 @@ class test_Command(ClassChecker):
assert type(ns) is plugable.NameSpace
assert len(ns) == len(args)
assert list(ns) == ['destination', 'source']
- assert type(ns.destination) is frontend.Param
- assert type(ns.source) is frontend.Param
+ assert type(ns.destination) is parameters.Str
+ assert type(ns.source) is parameters.Str
assert ns.destination.required is True
assert ns.destination.multivalue is False
assert ns.source.required is False
@@ -586,8 +171,8 @@ class test_Command(ClassChecker):
# Test TypeError:
e = raises(TypeError, self.get_instance, args=(u'whatever',))
- assert str(e) == \
- 'create_param() takes %r or %r; got %r' % (str, frontend.Param, u'whatever')
+ assert str(e) == TYPE_ERROR % (
+ 'spec', (str, parameters.Param), u'whatever', unicode)
# Test ValueError, required after optional:
e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2'))
@@ -627,8 +212,8 @@ class test_Command(ClassChecker):
assert type(ns) is plugable.NameSpace
assert len(ns) == len(options)
assert list(ns) == ['target', 'files']
- assert type(ns.target) is frontend.Param
- assert type(ns.files) is frontend.Param
+ assert type(ns.target) is parameters.Str
+ assert type(ns.files) is parameters.Str
assert ns.target.required is True
assert ns.target.multivalue is False
assert ns.files.required is False
@@ -640,17 +225,13 @@ class test_Command(ClassChecker):
"""
assert 'convert' in self.cls.__public__ # Public
kw = dict(
- option0='option0',
- option1='option1',
+ option0=u'1.5',
+ option1=u'7',
)
- expected = dict(kw)
- expected.update(dict(option0=u'option0', option1=u'option1'))
o = self.subcls()
o.finalize()
for (key, value) in o.convert(**kw).iteritems():
- v = expected[key]
- assert value == v
- assert type(value) is type(v)
+ assert_equal(unicode(kw[key]), value)
def test_normalize(self):
"""
@@ -671,22 +252,7 @@ class test_Command(ClassChecker):
Test the `ipalib.frontend.Command.get_default` method.
"""
assert 'get_default' in self.cls.__public__ # Public
- no_fill = dict(
- option0='value0',
- option1='value1',
- whatever='hello world',
- )
- fill = dict(
- default_from='the default',
- )
- default = dict(
- option0='the default',
- option1='the default',
- )
- sub = self.subcls()
- sub.finalize()
- assert sub.get_default(**no_fill) == {}
- assert sub.get_default(**fill) == default
+ # FIXME: Add an updated unit tests for get_default()
def test_validate(self):
"""
@@ -697,7 +263,7 @@ class test_Command(ClassChecker):
sub = self.subcls()
sub.finalize()
- # Check with valid args
+ # Check with valid values
okay = dict(
option0=u'option0',
option1=u'option1',
@@ -705,13 +271,13 @@ class test_Command(ClassChecker):
)
sub.validate(**okay)
- # Check with an invalid arg
+ # Check with an invalid value
fail = dict(okay)
fail['option0'] = u'whatever'
- e = raises(errors.RuleError, sub.validate, **fail)
- assert e.name == 'option0'
- assert e.value == u'whatever'
- assert e.error == 'must equal option0'
+ e = raises(errors2.ValidationError, sub.validate, **fail)
+ assert_equal(e.name, 'option0')
+ assert_equal(e.value, u'whatever')
+ assert_equal(e.error, u"must equal 'option0'")
assert e.rule.__class__.__name__ == 'Rule'
assert e.index is None
@@ -845,9 +411,9 @@ class test_LocalOrRemote(ClassChecker):
api.finalize()
cmd = api.Command.example
assert cmd() == ('execute', (None,), dict(server=False))
- assert cmd('var') == ('execute', (u'var',), dict(server=False))
+ assert cmd(u'var') == ('execute', (u'var',), dict(server=False))
assert cmd(server=True) == ('forward', (None,), dict(server=True))
- assert cmd('var', server=True) == \
+ assert cmd(u'var', server=True) == \
('forward', (u'var',), dict(server=True))
# Test when in_server=True (should always call execute):
@@ -856,9 +422,9 @@ class test_LocalOrRemote(ClassChecker):
api.finalize()
cmd = api.Command.example
assert cmd() == ('execute', (None,), dict(server=False))
- assert cmd('var') == ('execute', (u'var',), dict(server=False))
+ assert cmd(u'var') == ('execute', (u'var',), dict(server=False))
assert cmd(server=True) == ('execute', (None,), dict(server=True))
- assert cmd('var', server=True) == \
+ assert cmd(u'var', server=True) == \
('execute', (u'var',), dict(server=True))
@@ -974,7 +540,7 @@ class test_Object(ClassChecker):
assert len(ns) == 2, repr(ns)
assert list(ns) == ['banana', 'apple']
for p in ns():
- assert type(p) is frontend.Param
+ assert type(p) is parameters.Str
assert p.required is True
assert p.multivalue is False
@@ -1001,15 +567,13 @@ class test_Object(ClassChecker):
takes_params = (
'one',
'two',
- frontend.Param('three',
- primary_key=True,
- ),
+ parameters.Str('three', primary_key=True),
'four',
)
o = example2()
o.set_api(api)
pk = o.primary_key
- assert isinstance(pk, frontend.Param)
+ assert type(pk) is parameters.Str
assert pk.name == 'three'
assert pk.primary_key is True
assert o.params[2] is o.primary_key
@@ -1019,10 +583,10 @@ class test_Object(ClassChecker):
# Test with multiple primary_key:
class example3(self.cls):
takes_params = (
- frontend.Param('one', primary_key=True),
- frontend.Param('two', primary_key=True),
+ parameters.Str('one', primary_key=True),
+ parameters.Str('two', primary_key=True),
'three',
- frontend.Param('four', primary_key=True),
+ parameters.Str('four', primary_key=True),
)
o = example3()
e = raises(ValueError, o.set_api, api)
@@ -1155,12 +719,7 @@ class test_Property(ClassChecker):
Test the `ipalib.frontend.Property` class.
"""
assert self.cls.__bases__ == (frontend.Attribute,)
- assert isinstance(self.cls.type, ipa_types.Unicode)
- assert self.cls.required is False
- assert self.cls.multivalue is False
- assert self.cls.default is None
- assert self.cls.default_from is None
- assert self.cls.normalize is None
+ assert self.cls.klass is parameters.Str
def test_init(self):
"""
@@ -1170,7 +729,7 @@ class test_Property(ClassChecker):
assert len(o.rules) == 1
assert o.rules[0].__name__ == 'rule0_lowercase'
param = o.param
- assert isinstance(param, frontend.Param)
+ assert isinstance(param, parameters.Str)
assert param.name == 'givenname'
assert param.doc == 'User first name'
diff --git a/tests/test_ipalib/test_ipa_types.py b/tests/test_ipalib/test_ipa_types.py
deleted file mode 100644
index eb166069..00000000
--- a/tests/test_ipalib/test_ipa_types.py
+++ /dev/null
@@ -1,430 +0,0 @@
-# Authors:
-# Jason Gerard DeRose <jderose@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; version 2 only
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-Test the `ipalib.ipa_types` module.
-"""
-
-from tests.util import raises, getitem, no_set, no_del, read_only, ClassChecker
-from ipalib import ipa_types, errors, plugable
-
-
-def test_check_min_max():
- """
- Test the `ipalib.ipa_types.check_min_max` function.
- """
- f = ipa_types.check_min_max
- okay = [
- (None, -5),
- (-20, None),
- (-20, -5),
- ]
- for (l, h) in okay:
- assert f(l, h, 'low', 'high') is None
- fail_type = [
- '10',
- 10.0,
- 10L,
- True,
- False,
- object,
- ]
- for value in fail_type:
- e = raises(TypeError, f, value, None, 'low', 'high')
- assert str(e) == 'low must be an int or None, got: %r' % value
- e = raises(TypeError, f, None, value, 'low', 'high')
- assert str(e) == 'high must be an int or None, got: %r' % value
- fail_value = [
- (10, 5),
- (-5, -10),
- (5, -10),
- ]
- for (l, h) in fail_value:
- e = raises(ValueError, f, l, h, 'low', 'high')
- assert str(e) == 'low > high: low=%r, high=%r' % (l, h)
-
-
-class test_Type(ClassChecker):
- """
- Test the `ipalib.ipa_types.Type` class.
- """
- _cls = ipa_types.Type
-
- def test_class(self):
- """
- Test the `ipalib.ipa_types.Type` class.
- """
- assert self.cls.__bases__ == (plugable.ReadOnly,)
-
- def test_init(self):
- """
- Test the `ipalib.ipa_types.Type.__init__` method.
- """
- okay = (bool, int, float, unicode)
- for t in okay:
- o = self.cls(t)
- assert o.__islocked__() is True
- assert read_only(o, 'type') is t
- assert read_only(o, 'name') is 'Type'
-
- type_errors = (None, True, 8, 8.0, u'hello')
- for t in type_errors:
- e = raises(TypeError, self.cls, t)
- assert str(e) == '%r is not %r' % (type(t), type)
-
- value_errors = (long, complex, str, tuple, list, dict, set, frozenset)
- for t in value_errors:
- e = raises(ValueError, self.cls, t)
- assert str(e) == 'not an allowed type: %r' % t
-
- def test_validate(self):
- """
- Test the `ipalib.ipa_types.Type.validate` method.
- """
- o = self.cls(unicode)
- for value in (None, u'Hello', 'Hello', 42, False):
- assert o.validate(value) is None
-
-
-class test_Bool(ClassChecker):
- """
- Test the `ipalib.ipa_types.Bool` class.
- """
- _cls = ipa_types.Bool
-
- def test_class(self):
- """
- Test the `ipalib.ipa_types.Bool` class.
- """
- assert self.cls.__bases__ == (ipa_types.Type,)
-
- def test_init(self):
- """
- Test the `ipalib.ipa_types.Bool.__init__` method.
- """
- o = self.cls()
- assert o.__islocked__() is True
- assert read_only(o, 'type') is bool
- assert read_only(o, 'name') == 'Bool'
- assert read_only(o, 'true') == 'Yes'
- assert read_only(o, 'false') == 'No'
-
- keys = ('true', 'false')
- val = 'some value'
- for key in keys:
- # Check that kwarg sets appropriate attribute:
- o = self.cls(**{key: val})
- assert read_only(o, key) is val
- # Check that None raises TypeError:
- e = raises(TypeError, self.cls, **{key: None})
- assert str(e) == '`%s` cannot be None' % key
-
- # Check that ValueError is raise if true == false:
- e = raises(ValueError, self.cls, true=1L, false=1.0)
- assert str(e) == 'cannot be equal: true=1L, false=1.0'
-
- def test_call(self):
- """
- Test the `ipalib.ipa_types.Bool.__call__` method.
- """
- o = self.cls()
- assert o(True) is True
- assert o('Yes') is True
- assert o(False) is False
- assert o('No') is False
- for value in (0, 1, 'True', 'False', 'yes', 'no'):
- # value is not be converted, so None is returned
- assert o(value) is None
-
-
-class test_Int(ClassChecker):
- """
- Test the `ipalib.ipa_types.Int` class.
- """
- _cls = ipa_types.Int
-
- def test_class(self):
- """
- Test the `ipalib.ipa_types.Int` class.
- """
- assert self.cls.__bases__ == (ipa_types.Type,)
-
- def test_init(self):
- """
- Test the `ipalib.ipa_types.Int.__init__` method.
- """
- o = self.cls()
- assert o.__islocked__() is True
- assert read_only(o, 'type') is int
- assert read_only(o, 'name') == 'Int'
- assert read_only(o, 'min_value') is None
- assert read_only(o, 'max_value') is None
-
- okay = [
- (None, -5),
- (-20, None),
- (-20, -5),
- ]
- for (l, h) in okay:
- o = self.cls(min_value=l, max_value=h)
- assert o.min_value is l
- assert o.max_value is h
-
- fail_type = [
- '10',
- 10.0,
- 10L,
- True,
- False,
- object,
- ]
- for value in fail_type:
- e = raises(TypeError, self.cls, min_value=value)
- assert str(e) == (
- 'min_value must be an int or None, got: %r' % value
- )
- e = raises(TypeError, self.cls, max_value=value)
- assert str(e) == (
- 'max_value must be an int or None, got: %r' % value
- )
-
- fail_value = [
- (10, 5),
- (5, -5),
- (-5, -10),
- ]
- for (l, h) in fail_value:
- e = raises(ValueError, self.cls, min_value=l, max_value=h)
- assert str(e) == (
- 'min_value > max_value: min_value=%d, max_value=%d' % (l, h)
- )
-
- def test_call(self):
- """
- Test the `ipalib.ipa_types.Int.__call__` method.
- """
- o = self.cls()
-
- # Test calling with None
- e = raises(TypeError, o, None)
- assert str(e) == 'value cannot be None'
-
- # Test with values that can be converted:
- okay = [
- 3,
- '3',
- ' 3 ',
- 3L,
- 3.0,
- ]
- for value in okay:
- assert o(value) == 3
-
- # Test with values that cannot be converted:
- fail = [
- object,
- '3.0',
- '3L',
- 'whatever',
- ]
- for value in fail:
- assert o(value) is None
-
- def test_validate(self):
- """
- Test the `ipalib.ipa_types.Int.validate` method.
- """
- o = self.cls(min_value=2, max_value=7)
- assert o.validate(2) is None
- assert o.validate(5) is None
- assert o.validate(7) is None
- assert o.validate(1) == 'Cannot be smaller than 2'
- assert o.validate(8) == 'Cannot be larger than 7'
- for val in ['5', 5.0, 5L, None, True, False, object]:
- assert o.validate(val) == 'Must be an integer'
-
-
-class test_Unicode(ClassChecker):
- """
- Test the `ipalib.ipa_types.Unicode` class.
- """
- _cls = ipa_types.Unicode
-
- def test_class(self):
- """
- Test the `ipalib.ipa_types.Unicode` class.
- """
- assert self.cls.__bases__ == (ipa_types.Type,)
-
- def test_init(self):
- """
- Test the `ipalib.ipa_types.Unicode.__init__` method.
- """
- o = self.cls()
- assert o.__islocked__() is True
- assert read_only(o, 'type') is unicode
- assert read_only(o, 'name') == 'Unicode'
- assert read_only(o, 'min_length') is None
- assert read_only(o, 'max_length') is None
- assert read_only(o, 'pattern') is None
- assert read_only(o, 'regex') is None
-
- # Test min_length, max_length:
- okay = (
- (0, 1),
- (8, 8),
- )
- for (l, h) in okay:
- o = self.cls(min_length=l, max_length=h)
- assert o.min_length == l
- assert o.max_length == h
-
- fail_type = [
- '10',
- 10.0,
- 10L,
- True,
- False,
- object,
- ]
- for value in fail_type:
- e = raises(TypeError, self.cls, min_length=value)
- assert str(e) == (
- 'min_length must be an int or None, got: %r' % value
- )
- e = raises(TypeError, self.cls, max_length=value)
- assert str(e) == (
- 'max_length must be an int or None, got: %r' % value
- )
-
- fail_value = [
- (10, 5),
- (5, -5),
- (0, -10),
- ]
- for (l, h) in fail_value:
- e = raises(ValueError, self.cls, min_length=l, max_length=h)
- assert str(e) == (
- 'min_length > max_length: min_length=%d, max_length=%d' % (l, h)
- )
-
- for (key, lower) in [('min_length', 0), ('max_length', 1)]:
- value = lower - 1
- kw = {key: value}
- e = raises(ValueError, self.cls, **kw)
- assert str(e) == '%s must be >= %d, got: %d' % (key, lower, value)
-
- # Test pattern:
- okay = [
- '(hello|world)',
- u'(take the blue pill|take the red pill)',
- ]
- for value in okay:
- o = self.cls(pattern=value)
- assert o.pattern is value
- assert o.regex is not None
-
- fail = [
- 42,
- True,
- False,
- object,
- ]
- for value in fail:
- e = raises(TypeError, self.cls, pattern=value)
- assert str(e) == (
- 'pattern must be a basestring or None, got: %r' % value
- )
-
- # Test regex:
- pat = '^(hello|world)$'
- o = self.cls(pattern=pat)
- for value in ('hello', 'world'):
- m = o.regex.match(value)
- assert m.group(1) == value
- for value in ('hello beautiful', 'world!'):
- assert o.regex.match(value) is None
-
- def test_validate(self):
- """
- Test the `ipalib.ipa_types.Unicode.validate` method.
- """
- pat = '^a_*b$'
- o = self.cls(min_length=3, max_length=4, pattern=pat)
- assert o.validate(u'a_b') is None
- assert o.validate(u'a__b') is None
- assert o.validate('a_b') == 'Must be a string'
- assert o.validate(u'ab') == 'Must be at least 3 characters long'
- assert o.validate(u'a___b') == 'Can be at most 4 characters long'
- assert o.validate(u'a-b') == 'Must match %r' % pat
- assert o.validate(u'a--b') == 'Must match %r' % pat
-
-
-class test_Enum(ClassChecker):
- """
- Test the `ipalib.ipa_types.Enum` class.
- """
- _cls = ipa_types.Enum
-
- def test_class(self):
- """
- Test the `ipalib.ipa_types.Enum` class.
- """
- assert self.cls.__bases__ == (ipa_types.Type,)
-
- def test_init(self):
- """
- Test the `ipalib.ipa_types.Enum.__init__` method.
- """
- for t in (unicode, int, float):
- values = (t(1), t(2), t(3))
- o = self.cls(*values)
- assert o.__islocked__() is True
- assert read_only(o, 'type') is t
- assert read_only(o, 'name') is 'Enum'
- assert read_only(o, 'values') == values
- assert read_only(o, 'frozenset') == frozenset(values)
-
- # Check that ValueError is raised when no values are given:
- e = raises(ValueError, self.cls)
- assert str(e) == 'Enum requires at least one value'
-
- # Check that TypeError is raised when type of first value is not
- # allowed:
- e = raises(TypeError, self.cls, 'hello')
- assert str(e) == '%r: %r not unicode, int, nor float' % ('hello', str)
- #self.cls('hello')
-
- # Check that TypeError is raised when subsequent values aren't same
- # type as first:
- e = raises(TypeError, self.cls, u'hello', 'world')
- assert str(e) == '%r: %r is not %r' % ('world', str, unicode)
-
- def test_validate(self):
- """
- Test the `ipalib.ipa_types.Enum.validate` method.
- """
- values = (u'hello', u'naughty', u'nurse')
- o = self.cls(*values)
- for value in values:
- assert o.validate(value) is None
- assert o.validate(str(value)) == 'Incorrect type'
- for value in (u'one fish', u'two fish'):
- assert o.validate(value) == 'Invalid value'
- assert o.validate(str(value)) == 'Incorrect type'
diff --git a/tests/test_ipalib/test_parameter.py b/tests/test_ipalib/test_parameter.py
deleted file mode 100644
index e2016f06..00000000
--- a/tests/test_ipalib/test_parameter.py
+++ /dev/null
@@ -1,531 +0,0 @@
-# Authors:
-# Jason Gerard DeRose <jderose@redhat.com>
-#
-# Copyright (C) 2008 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation; version 2 only
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-"""
-Test the `ipalib.parameter` module.
-"""
-
-from tests.util import raises, ClassChecker, read_only
-from tests.util import dummy_ugettext, assert_equal
-from tests.data import binary_bytes, utf8_bytes, unicode_str
-from ipalib import parameter, request
-from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS
-
-
-class test_DefaultFrom(ClassChecker):
- """
- Test the `ipalib.parameter.DefaultFrom` class.
- """
- _cls = parameter.DefaultFrom
-
- def test_init(self):
- """
- Test the `ipalib.parameter.DefaultFrom.__init__` method.
- """
- def callback(*args):
- return args
- keys = ('givenname', 'sn')
- o = self.cls(callback, *keys)
- assert read_only(o, 'callback') is callback
- assert read_only(o, 'keys') == keys
- lam = lambda first, last: first[0] + last
- o = self.cls(lam)
- assert read_only(o, 'keys') == ('first', 'last')
-
- # Test that TypeError is raised when callback isn't callable:
- e = raises(TypeError, self.cls, 'whatever')
- assert str(e) == CALLABLE_ERROR % ('callback', 'whatever', str)
-
- # Test that TypeError is raised when a key isn't an str:
- e = raises(TypeError, self.cls, callback, 'givenname', 17)
- assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
-
- def test_call(self):
- """
- Test the `ipalib.parameter.DefaultFrom.__call__` method.
- """
- def callback(givenname, sn):
- return givenname[0] + sn[0]
- keys = ('givenname', 'sn')
- o = self.cls(callback, *keys)
- kw = dict(
- givenname='John',
- sn='Public',
- hello='world',
- )
- assert o(**kw) == 'JP'
- assert o() is None
- for key in ('givenname', 'sn'):
- kw_copy = dict(kw)
- del kw_copy[key]
- assert o(**kw_copy) is None
-
- # Test using implied keys:
- o = self.cls(lambda first, last: first[0] + last)
- assert o(first='john', last='doe') == 'jdoe'
- assert o(first='', last='doe') is None
- assert o(one='john', two='doe') is None
-
- # Test that co_varnames slice is used:
- def callback2(first, last):
- letter = first[0]
- return letter + last
- o = self.cls(callback2)
- assert o.keys == ('first', 'last')
- assert o(first='john', last='doe') == 'jdoe'
-
-
-def test_parse_param_spec():
- """
- Test the `ipalib.parameter.parse_param_spec` function.
- """
- f = parameter.parse_param_spec
- assert f('name') == ('name', dict(required=True, multivalue=False))
- 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 *not* treated special:
- assert f('name^') == ('name^', dict(required=True, multivalue=False))
-
- # Test that TypeError is raised if spec isn't an str:
- e = raises(TypeError, f, u'name?')
- assert str(e) == TYPE_ERROR % ('spec', str, u'name?', unicode)
-
- # Test that ValueError is raised if len(spec) < 2:
- e = raises(ValueError, f, 'n')
- assert str(e) == "spec must be at least 2 characters; got 'n'"
-
-
-class test_Param(ClassChecker):
- """
- Test the `ipalib.parameter.Param` class.
- """
- _cls = parameter.Param
-
- def test_init(self):
- """
- Test the `ipalib.parameter.Param.__init__` method.
- """
- name = 'my_param'
- o = self.cls(name)
- assert o.param_spec is name
- assert o.name is name
- assert o.nice == "Param('my_param')"
- assert o.__islocked__() is True
-
- # Test default rules:
- assert o.rules == tuple()
- assert o.class_rules == tuple()
- assert o.all_rules == tuple()
-
- # Test default kwarg values:
- assert o.cli_name is name
- assert o.label is None
- 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()
-
- # Test that ValueError is raised when a kwarg from a subclass
- # conflicts with an attribute:
- class Subclass(self.cls):
- 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:
- 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()
- 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))
- # Test with None:
- kw = {key: None}
- Subclass('my_param', **kw)
-
- # Test when using unknown kwargs:
- e = raises(TypeError, self.cls, 'my_param',
- flags=['hello', 'world'],
- whatever=u'Hooray!',
- )
- assert str(e) == \
- "Param('my_param'): takes no such kwargs: 'whatever'"
- e = raises(TypeError, self.cls, 'my_param', great='Yes', ape='he is!')
- assert str(e) == \
- "Param('my_param'): takes no such kwargs: 'ape', 'great'"
-
- def test_repr(self):
- """
- Test the `ipalib.parameter.Param.__repr__` method.
- """
- for name in ['name', 'name?', 'name*', 'name+']:
- o = self.cls(name)
- assert repr(o) == 'Param(%r)' % name
- o = self.cls('name', required=False)
- assert repr(o) == "Param('name', required=False)"
- o = self.cls('name', multivalue=True)
- assert repr(o) == "Param('name', multivalue=True)"
-
- def test_get_label(self):
- """
- Test the `ipalib.parameter.get_label` method.
- """
- context = request.context
- cli_name = 'the_cli_name'
- message = 'The Label'
- label = lambda _: _(message)
- o = self.cls('name', cli_name=cli_name, label=label)
- assert o.label is label
-
- ## Scenario 1: label=callable (a lambda form)
-
- # Test with no context.ugettext:
- assert not hasattr(context, 'ugettext')
- assert_equal(o.get_label(), u'The Label')
-
- # Test with dummy context.ugettext:
- assert not hasattr(context, 'ugettext')
- dummy = dummy_ugettext()
- context.ugettext = dummy
- assert o.get_label() is dummy.translation
- assert dummy.message is message
- del context.ugettext
-
- ## Scenario 2: label=None
- o = self.cls('name', cli_name=cli_name)
- assert o.label is None
-
- # Test with no context.ugettext:
- assert not hasattr(context, 'ugettext')
- assert_equal(o.get_label(), u'the_cli_name')
-
- # Test with dummy context.ugettext:
- assert not hasattr(context, 'ugettext')
- dummy = dummy_ugettext()
- context.ugettext = dummy
- assert_equal(o.get_label(), u'the_cli_name')
- assert not hasattr(dummy, 'message')
-
- # Cleanup
- del context.ugettext
- assert not hasattr(context, 'ugettext')
-
- def test_convert(self):
- """
- Test the `ipalib.parameter.Param.convert` method.
- """
- okay = ('Hello', u'Hello', 0, 4.2, True, False)
- class Subclass(self.cls):
- def _convert_scalar(self, value, index=None):
- return value
-
- # Test when multivalue=False:
- o = Subclass('my_param')
- for value in NULLS:
- assert o.convert(value) is None
- for value in okay:
- assert o.convert(value) is value
-
- # Test when multivalue=True:
- o = Subclass('my_param', multivalue=True)
- for value in NULLS:
- assert o.convert(value) is None
- assert o.convert(okay) == okay
- assert o.convert(NULLS) is None
- assert o.convert(okay + NULLS) == okay
- assert o.convert(NULLS + okay) == okay
- for value in okay:
- assert o.convert(value) == (value,)
- assert o.convert([None, value]) == (value,)
- assert o.convert([value, None]) == (value,)
-
- def test_convert_scalar(self):
- """
- Test the `ipalib.parameter.Param._convert_scalar` method.
- """
- 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')
- e = raises(NotImplementedError, o._convert_scalar, 'some value')
- assert str(e) == 'Subclass._convert_scalar()'
-
-
-class test_Bytes(ClassChecker):
- """
- Test the `ipalib.parameter.Bytes` class.
- """
- _cls = parameter.Bytes
-
- def test_init(self):
- """
- Test the `ipalib.parameter.Bytes.__init__` method.
- """
- o = self.cls('my_bytes')
- assert o.type is str
- assert o.rules == tuple()
- assert o.class_rules == tuple()
- assert o.all_rules == tuple()
- assert o.minlength is None
- assert o.maxlength is None
- assert o.length is None
- assert o.pattern is None
-
- # Test mixing length with minlength or maxlength:
- o = self.cls('my_bytes', length=5)
- assert o.length == 5
- assert len(o.class_rules) == 1
- assert len(o.rules) == 0
- assert len(o.all_rules) == 1
- permutations = [
- dict(minlength=3),
- dict(maxlength=7),
- dict(minlength=3, maxlength=7),
- ]
- for kw in permutations:
- o = self.cls('my_bytes', **kw)
- assert len(o.class_rules) == len(kw)
- assert len(o.rules) == 0
- assert len(o.all_rules) == len(kw)
- for (key, value) in kw.iteritems():
- assert getattr(o, key) == value
- e = raises(ValueError, self.cls, 'my_bytes', length=5, **kw)
- assert str(e) == \
- "Bytes('my_bytes'): cannot mix length with minlength or maxlength"
-
- # Test when minlength or maxlength are less than 1:
- e = raises(ValueError, self.cls, 'my_bytes', minlength=0)
- assert str(e) == "Bytes('my_bytes'): minlength must be >= 1; got 0"
- e = raises(ValueError, self.cls, 'my_bytes', maxlength=0)
- assert str(e) == "Bytes('my_bytes'): maxlength must be >= 1; got 0"
-
- # Test when minlength > maxlength:
- e = raises(ValueError, self.cls, 'my_bytes', minlength=22, maxlength=15)
- assert str(e) == \
- "Bytes('my_bytes'): minlength > maxlength (minlength=22, maxlength=15)"
-
- # Test when minlength == maxlength
- e = raises(ValueError, self.cls, 'my_bytes', minlength=7, maxlength=7)
- assert str(e) == \
- "Bytes('my_bytes'): minlength == maxlength; use length=7 instead"
-
- def test_rule_minlength(self):
- """
- Test the `ipalib.parameter.Bytes._rule_minlength` method.
- """
- name = 'My Bytes'
- o = self.cls('my_bytes', minlength=3)
- assert o.minlength == 3
- m = o._rule_minlength
- translation = u'name=%(name)r, minlength=%(minlength)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in ('abc', 'four', '12345'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with a failing value:
- assert_equal(
- m(dummy, name, 'ab'),
- translation % dict(name=name, minlength=3),
- )
- assert dummy.message == \
- '%(name)s must be at least %(minlength)d bytes'
-
- def test_rule_maxlength(self):
- """
- Test the `ipalib.parameter.Bytes._rule_maxlength` method.
- """
- name = 'My Bytes'
- o = self.cls('my_bytes', maxlength=4)
- assert o.maxlength == 4
- m = o._rule_maxlength
- translation = u'name=%(name)r, maxlength=%(maxlength)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in ('ab', '123', 'four'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with a failing value:
- assert_equal(
- m(dummy, name, '12345'),
- translation % dict(name=name, maxlength=4),
- )
- assert dummy.message == \
- '%(name)s can be at most %(maxlength)d bytes'
-
- def test_rule_length(self):
- """
- Test the `ipalib.parameter.Bytes._rule_length` method.
- """
- name = 'My Bytes'
- o = self.cls('my_bytes', length=4)
- assert o.length == 4
- m = o._rule_length
- translation = u'name=%(name)r, length=%(length)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in ('1234', 'four'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with failing values:
- for value in ('ab', '123', '12345', 'abcdef'):
- assert_equal(
- m(dummy, name, value),
- translation % dict(name=name, length=4),
- )
- assert dummy.message == \
- '%(name)s must be exactly %(length)d bytes'
- dummy = dummy_ugettext(translation)
-
-
-class test_Str(ClassChecker):
- """
- Test the `ipalib.parameter.Str` class.
- """
- _cls = parameter.Str
-
- def test_init(self):
- """
- Test the `ipalib.parameter.Str.__init__` method.
- """
- o = self.cls('my_str')
- assert o.type is unicode
- assert o.minlength is None
- assert o.maxlength is None
- assert o.length is None
- assert o.pattern is None
-
- def test_convert_scalar(self):
- """
- Test the `ipalib.parameter.Str._convert_scalar` method.
- """
- o = self.cls('my_str')
- for value in (u'Hello', 42, 1.2, True):
- assert o._convert_scalar(value) == unicode(value)
- for value in ('Hello', (None,), [u'42', '42'], dict(hello=u'world')):
- e = raises(TypeError, o._convert_scalar, value)
- assert str(e) == \
- 'Can only implicitly convert int, float, or bool; got %r' % value
-
- def test_rule_minlength(self):
- """
- Test the `ipalib.parameter.Str._rule_minlength` method.
- """
- name = 'My Str'
- o = self.cls('my_str', minlength=3)
- assert o.minlength == 3
- m = o._rule_minlength
- translation = u'name=%(name)r, minlength=%(minlength)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in (u'abc', u'four', u'12345'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with a failing value:
- assert_equal(
- m(dummy, name, u'ab'),
- translation % dict(name=name, minlength=3),
- )
- assert dummy.message == \
- '%(name)s must be at least %(minlength)d characters'
-
- def test_rule_maxlength(self):
- """
- Test the `ipalib.parameter.Str._rule_maxlength` method.
- """
- name = 'My Str'
- o = self.cls('my_str', maxlength=4)
- assert o.maxlength == 4
- m = o._rule_maxlength
- translation = u'name=%(name)r, maxlength=%(maxlength)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in (u'ab', u'123', u'four'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with a failing value:
- assert_equal(
- m(dummy, name, u'12345'),
- translation % dict(name=name, maxlength=4),
- )
- assert dummy.message == \
- '%(name)s can be at most %(maxlength)d characters'
-
- def test_rule_length(self):
- """
- Test the `ipalib.parameter.Str._rule_length` method.
- """
- name = 'My Str'
- o = self.cls('my_str', length=4)
- assert o.length == 4
- m = o._rule_length
- translation = u'name=%(name)r, length=%(length)r'
- dummy = dummy_ugettext(translation)
- assert dummy.translation is translation
-
- # Test with passing values:
- for value in (u'1234', u'four'):
- assert m(dummy, name, value) is None
- assert not hasattr(dummy, 'message')
-
- # Test with failing values:
- for value in (u'ab', u'123', u'12345', u'abcdef'):
- assert_equal(
- m(dummy, name, value),
- translation % dict(name=name, length=4),
- )
- assert dummy.message == \
- '%(name)s must be exactly %(length)d characters'
- dummy = dummy_ugettext(translation)
diff --git a/tests/test_ipalib/test_parameters.py b/tests/test_ipalib/test_parameters.py
new file mode 100644
index 00000000..f9e370fe
--- /dev/null
+++ b/tests/test_ipalib/test_parameters.py
@@ -0,0 +1,944 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Test the `ipalib.parameters` module.
+"""
+
+from types import NoneType
+from inspect import isclass
+from tests.util import raises, ClassChecker, read_only
+from tests.util import dummy_ugettext, assert_equal
+from tests.data import binary_bytes, utf8_bytes, unicode_str
+from ipalib import parameters, request, errors2
+from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS
+
+
+class test_DefaultFrom(ClassChecker):
+ """
+ Test the `ipalib.parameters.DefaultFrom` class.
+ """
+ _cls = parameters.DefaultFrom
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.DefaultFrom.__init__` method.
+ """
+ def callback(*args):
+ return args
+ keys = ('givenname', 'sn')
+ o = self.cls(callback, *keys)
+ assert read_only(o, 'callback') is callback
+ assert read_only(o, 'keys') == keys
+ lam = lambda first, last: first[0] + last
+ o = self.cls(lam)
+ assert read_only(o, 'keys') == ('first', 'last')
+
+ # Test that TypeError is raised when callback isn't callable:
+ e = raises(TypeError, self.cls, 'whatever')
+ assert str(e) == CALLABLE_ERROR % ('callback', 'whatever', str)
+
+ # Test that TypeError is raised when a key isn't an str:
+ e = raises(TypeError, self.cls, callback, 'givenname', 17)
+ assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
+
+ def test_call(self):
+ """
+ Test the `ipalib.parameters.DefaultFrom.__call__` method.
+ """
+ def callback(givenname, sn):
+ return givenname[0] + sn[0]
+ keys = ('givenname', 'sn')
+ o = self.cls(callback, *keys)
+ kw = dict(
+ givenname='John',
+ sn='Public',
+ hello='world',
+ )
+ assert o(**kw) == 'JP'
+ assert o() is None
+ for key in ('givenname', 'sn'):
+ kw_copy = dict(kw)
+ del kw_copy[key]
+ assert o(**kw_copy) is None
+
+ # Test using implied keys:
+ o = self.cls(lambda first, last: first[0] + last)
+ assert o(first='john', last='doe') == 'jdoe'
+ assert o(first='', last='doe') is None
+ assert o(one='john', two='doe') is None
+
+ # Test that co_varnames slice is used:
+ def callback2(first, last):
+ letter = first[0]
+ return letter + last
+ o = self.cls(callback2)
+ assert o.keys == ('first', 'last')
+ assert o(first='john', last='doe') == 'jdoe'
+
+
+def test_parse_param_spec():
+ """
+ Test the `ipalib.parameters.parse_param_spec` function.
+ """
+ f = parameters.parse_param_spec
+ assert f('name') == ('name', dict(required=True, multivalue=False))
+ 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 *not* treated special:
+ assert f('name^') == ('name^', dict(required=True, multivalue=False))
+
+ # Test that TypeError is raised if spec isn't an str:
+ e = raises(TypeError, f, u'name?')
+ assert str(e) == TYPE_ERROR % ('spec', str, u'name?', unicode)
+
+ # Test that ValueError is raised if len(spec) < 2:
+ e = raises(ValueError, f, 'n')
+ assert str(e) == "spec must be at least 2 characters; got 'n'"
+
+
+class DummyRule(object):
+ def __init__(self, error=None):
+ assert error is None or type(error) is unicode
+ self.error = error
+ self.reset()
+
+ def __call__(self, *args):
+ self.calls.append(args)
+ return self.error
+
+ def reset(self):
+ self.calls = []
+
+
+class test_Param(ClassChecker):
+ """
+ Test the `ipalib.parameters.Param` class.
+ """
+ _cls = parameters.Param
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.Param.__init__` method.
+ """
+ name = 'my_param'
+ o = self.cls(name)
+ assert o.param_spec is name
+ assert o.name is name
+ assert o.nice == "Param('my_param')"
+ assert o.__islocked__() is True
+
+ # Test default rules:
+ assert o.rules == tuple()
+ assert o.class_rules == tuple()
+ assert o.all_rules == tuple()
+
+ # Test default kwarg values:
+ assert o.cli_name is name
+ assert o.label is None
+ 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.create_default is None
+ assert o._get_default is None
+ assert o.autofill is False
+ assert o.query is False
+ assert o.flags == frozenset()
+
+ # Test that ValueError is raised when a kwarg from a subclass
+ # conflicts with an attribute:
+ class Subclass(self.cls):
+ 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:
+ 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()
+ 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))
+ # Test with None:
+ kw = {key: None}
+ Subclass('my_param', **kw)
+
+ # Test when using unknown kwargs:
+ e = raises(TypeError, self.cls, 'my_param',
+ flags=['hello', 'world'],
+ whatever=u'Hooray!',
+ )
+ assert str(e) == \
+ "Param('my_param'): takes no such kwargs: 'whatever'"
+ e = raises(TypeError, self.cls, 'my_param', great='Yes', ape='he is!')
+ assert str(e) == \
+ "Param('my_param'): takes no such kwargs: 'ape', 'great'"
+
+ # Test that ValueError is raised if you provide both default_from and
+ # create_default:
+ e = raises(ValueError, self.cls, 'my_param',
+ default_from=lambda first, last: first[0] + last,
+ create_default=lambda **kw: 'The Default'
+ )
+ assert str(e) == '%s: cannot have both %r and %r' % (
+ "Param('my_param')", 'default_from', 'create_default',
+ )
+
+ # Test that _get_default gets set:
+ call1 = lambda first, last: first[0] + last
+ call2 = lambda **kw: 'The Default'
+ o = self.cls('my_param', default_from=call1)
+ assert o.default_from.callback is call1
+ assert o._get_default is o.default_from
+ o = self.cls('my_param', create_default=call2)
+ assert o.create_default is call2
+ assert o._get_default is call2
+
+ def test_repr(self):
+ """
+ Test the `ipalib.parameters.Param.__repr__` method.
+ """
+ for name in ['name', 'name?', 'name*', 'name+']:
+ o = self.cls(name)
+ assert repr(o) == 'Param(%r)' % name
+ o = self.cls('name', required=False)
+ assert repr(o) == "Param('name', required=False)"
+ o = self.cls('name', multivalue=True)
+ assert repr(o) == "Param('name', multivalue=True)"
+
+ def test_clone(self):
+ """
+ Test the `ipalib.parameters.Param.clone` method.
+ """
+ # Test with the defaults
+ orig = self.cls('my_param')
+ clone = orig.clone()
+ assert clone is not orig
+ assert type(clone) is self.cls
+ assert clone.name is orig.name
+ for (key, kind, default) in self.cls.kwargs:
+ assert getattr(clone, key) is getattr(orig, key)
+
+ # Test with a param spec:
+ orig = self.cls('my_param*')
+ assert orig.param_spec == 'my_param*'
+ clone = orig.clone()
+ assert clone.param_spec == 'my_param'
+ assert clone is not orig
+ assert type(clone) is self.cls
+ for (key, kind, default) in self.cls.kwargs:
+ assert getattr(clone, key) is getattr(orig, key)
+
+ # Test with overrides:
+ orig = self.cls('my_param*')
+ assert orig.required is False
+ assert orig.multivalue is True
+ clone = orig.clone(required=True)
+ assert clone is not orig
+ assert type(clone) is self.cls
+ assert clone.required is True
+ assert clone.multivalue is True
+ assert clone.param_spec == 'my_param'
+ assert clone.name == 'my_param'
+
+ def test_get_label(self):
+ """
+ Test the `ipalib.parameters.get_label` method.
+ """
+ context = request.context
+ cli_name = 'the_cli_name'
+ message = 'The Label'
+ label = lambda _: _(message)
+ o = self.cls('name', cli_name=cli_name, label=label)
+ assert o.label is label
+
+ ## Scenario 1: label=callable (a lambda form)
+
+ # Test with no context.ugettext:
+ assert not hasattr(context, 'ugettext')
+ assert_equal(o.get_label(), u'The Label')
+
+ # Test with dummy context.ugettext:
+ assert not hasattr(context, 'ugettext')
+ dummy = dummy_ugettext()
+ context.ugettext = dummy
+ assert o.get_label() is dummy.translation
+ assert dummy.message is message
+ del context.ugettext
+
+ ## Scenario 2: label=None
+ o = self.cls('name', cli_name=cli_name)
+ assert o.label is None
+
+ # Test with no context.ugettext:
+ assert not hasattr(context, 'ugettext')
+ assert_equal(o.get_label(), u'the_cli_name')
+
+ # Test with dummy context.ugettext:
+ assert not hasattr(context, 'ugettext')
+ dummy = dummy_ugettext()
+ context.ugettext = dummy
+ assert_equal(o.get_label(), u'the_cli_name')
+ assert not hasattr(dummy, 'message')
+
+ # Cleanup
+ del context.ugettext
+ assert not hasattr(context, 'ugettext')
+
+ def test_convert(self):
+ """
+ Test the `ipalib.parameters.Param.convert` method.
+ """
+ okay = ('Hello', u'Hello', 0, 4.2, True, False)
+ class Subclass(self.cls):
+ def _convert_scalar(self, value, index=None):
+ return value
+
+ # Test when multivalue=False:
+ o = Subclass('my_param')
+ for value in NULLS:
+ assert o.convert(value) is None
+ for value in okay:
+ assert o.convert(value) is value
+
+ # Test when multivalue=True:
+ o = Subclass('my_param', multivalue=True)
+ for value in NULLS:
+ assert o.convert(value) is None
+ assert o.convert(okay) == okay
+ assert o.convert(NULLS) is None
+ assert o.convert(okay + NULLS) == okay
+ assert o.convert(NULLS + okay) == okay
+ for value in okay:
+ assert o.convert(value) == (value,)
+ assert o.convert([None, value]) == (value,)
+ assert o.convert([value, None]) == (value,)
+
+ def test_convert_scalar(self):
+ """
+ Test the `ipalib.parameters.Param._convert_scalar` method.
+ """
+ dummy = dummy_ugettext()
+
+ # Test with correct type:
+ o = self.cls('my_param')
+ assert o._convert_scalar(None) is None
+ assert dummy.called() is False
+ # Test with incorrect type
+ e = raises(errors2.ConversionError, o._convert_scalar, 'hello', index=17)
+
+ def test_validate(self):
+ """
+ Test the `ipalib.parameters.Param.validate` method.
+ """
+
+ # Test in default state (with no rules, no kwarg):
+ o = self.cls('my_param')
+ e = raises(errors2.RequirementError, o.validate, None)
+ assert e.name == 'my_param'
+
+ # Test with required=False
+ o = self.cls('my_param', required=False)
+ assert o.required is False
+ assert o.validate(None) is None
+
+ # Test with query=True:
+ o = self.cls('my_param', query=True)
+ assert o.query is True
+ assert o.validate(None) is None
+
+ # Test with multivalue=True:
+ o = self.cls('my_param', multivalue=True)
+ e = raises(TypeError, o.validate, [])
+ assert str(e) == TYPE_ERROR % ('value', tuple, [], list)
+ e = raises(ValueError, o.validate, tuple())
+ assert str(e) == 'value: empty tuple must be converted to None'
+
+ # Test with wrong (scalar) type:
+ e = raises(TypeError, o.validate, (None, None, 42, None))
+ assert str(e) == TYPE_ERROR % ('value[2]', NoneType, 42, int)
+ o = self.cls('my_param')
+ e = raises(TypeError, o.validate, 'Hello')
+ assert str(e) == TYPE_ERROR % ('value', NoneType, 'Hello', str)
+
+ class Example(self.cls):
+ type = int
+
+ # Test with some rules and multivalue=False
+ pass1 = DummyRule()
+ pass2 = DummyRule()
+ fail = DummyRule(u'no good')
+ o = Example('example', pass1, pass2)
+ assert o.multivalue is False
+ assert o.validate(11) is None
+ assert pass1.calls == [(request.ugettext, 11)]
+ assert pass2.calls == [(request.ugettext, 11)]
+ pass1.reset()
+ pass2.reset()
+ o = Example('example', pass1, pass2, fail)
+ e = raises(errors2.ValidationError, o.validate, 42)
+ assert e.name == 'example'
+ assert e.error == u'no good'
+ assert e.index is None
+ assert pass1.calls == [(request.ugettext, 42)]
+ assert pass2.calls == [(request.ugettext, 42)]
+ assert fail.calls == [(request.ugettext, 42)]
+
+ # Test with some rules and multivalue=True
+ pass1 = DummyRule()
+ pass2 = DummyRule()
+ fail = DummyRule(u'this one is not good')
+ o = Example('example', pass1, pass2, multivalue=True)
+ assert o.multivalue is True
+ assert o.validate((3, 9)) is None
+ assert pass1.calls == [
+ (request.ugettext, 3),
+ (request.ugettext, 9),
+ ]
+ assert pass2.calls == [
+ (request.ugettext, 3),
+ (request.ugettext, 9),
+ ]
+ pass1.reset()
+ pass2.reset()
+ o = Example('multi_example', pass1, pass2, fail, multivalue=True)
+ assert o.multivalue is True
+ e = raises(errors2.ValidationError, o.validate, (3, 9))
+ assert e.name == 'multi_example'
+ assert e.error == u'this one is not good'
+ assert e.index == 0
+ assert pass1.calls == [(request.ugettext, 3)]
+ assert pass2.calls == [(request.ugettext, 3)]
+ assert fail.calls == [(request.ugettext, 3)]
+
+ def test_validate_scalar(self):
+ """
+ Test the `ipalib.parameters.Param._validate_scalar` method.
+ """
+ class MyParam(self.cls):
+ type = bool
+ okay = DummyRule()
+ o = MyParam('my_param', okay)
+
+ # Test that TypeError is appropriately raised:
+ e = raises(TypeError, o._validate_scalar, 0)
+ assert str(e) == TYPE_ERROR % ('value', bool, 0, int)
+ e = raises(TypeError, o._validate_scalar, 'Hi', index=4)
+ assert str(e) == TYPE_ERROR % ('value[4]', bool, 'Hi', str)
+ e = raises(TypeError, o._validate_scalar, True, index=3.0)
+ assert str(e) == TYPE_ERROR % ('index', int, 3.0, float)
+
+ # Test with passing rule:
+ assert o._validate_scalar(True, index=None) is None
+ assert o._validate_scalar(False, index=None) is None
+ assert okay.calls == [
+ (request.ugettext, True),
+ (request.ugettext, False),
+ ]
+
+ # Test with a failing rule:
+ okay = DummyRule()
+ fail = DummyRule(u'this describes the error')
+ o = MyParam('my_param', okay, fail)
+ e = raises(errors2.ValidationError, o._validate_scalar, True)
+ assert e.name == 'my_param'
+ assert e.error == u'this describes the error'
+ assert e.index is None
+ e = raises(errors2.ValidationError, o._validate_scalar, False, index=2)
+ assert e.name == 'my_param'
+ assert e.error == u'this describes the error'
+ assert e.index == 2
+ assert okay.calls == [
+ (request.ugettext, True),
+ (request.ugettext, False),
+ ]
+ assert fail.calls == [
+ (request.ugettext, True),
+ (request.ugettext, False),
+ ]
+
+ def test_get_default(self):
+ """
+ Test the `ipalib.parameters.Param._get_default` method.
+ """
+ class PassThrough(object):
+ value = None
+
+ def __call__(self, value):
+ assert self.value is None
+ assert value is not None
+ self.value = value
+ return value
+
+ def reset(self):
+ assert self.value is not None
+ self.value = None
+
+ class Str(self.cls):
+ type = unicode
+
+ def __init__(self, name, **kw):
+ self._convert_scalar = PassThrough()
+ super(Str, self).__init__(name, **kw)
+
+ # Test with only a static default:
+ o = Str('my_str',
+ normalizer=PassThrough(),
+ default=u'Static Default',
+ )
+ assert_equal(o.get_default(), u'Static Default')
+ assert o._convert_scalar.value is None
+ assert o.normalizer.value is None
+
+ # Test with default_from:
+ o = Str('my_str',
+ normalizer=PassThrough(),
+ default=u'Static Default',
+ default_from=lambda first, last: first[0] + last,
+ )
+ assert_equal(o.get_default(), u'Static Default')
+ assert o._convert_scalar.value is None
+ assert o.normalizer.value is None
+ default = o.get_default(first=u'john', last='doe')
+ assert_equal(default, u'jdoe')
+ assert o._convert_scalar.value is default
+ assert o.normalizer.value is default
+
+ # Test with create_default:
+ o = Str('my_str',
+ normalizer=PassThrough(),
+ default=u'Static Default',
+ create_default=lambda **kw: u'The created default',
+ )
+ default = o.get_default(first=u'john', last='doe')
+ assert_equal(default, u'The created default')
+ assert o._convert_scalar.value is default
+ assert o.normalizer.value is default
+
+
+class test_Flag(ClassChecker):
+ """
+ Test the `ipalib.parameters.Flag` class.
+ """
+ _cls = parameters.Flag
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.Flag.__init__` method.
+ """
+ # Test with no kwargs:
+ o = self.cls('my_flag')
+ assert o.type is bool
+ assert isinstance(o, parameters.Bool)
+ assert o.autofill is True
+ assert o.default is False
+
+ # Test that TypeError is raise if default is not a bool:
+ e = raises(TypeError, self.cls, 'my_flag', default=None)
+ assert str(e) == TYPE_ERROR % ('default', bool, None, NoneType)
+
+ # Test with autofill=False, default=True
+ o = self.cls('my_flag', autofill=False, default=True)
+ assert o.autofill is True
+ assert o.default is True
+
+ # Test when cloning:
+ orig = self.cls('my_flag')
+ for clone in [orig.clone(), orig.clone(autofill=False)]:
+ assert clone.autofill is True
+ assert clone.default is False
+ assert clone is not orig
+ assert type(clone) is self.cls
+
+ # Test when cloning with default=True/False
+ orig = self.cls('my_flag')
+ assert orig.clone().default is False
+ assert orig.clone(default=True).default is True
+ orig = self.cls('my_flag', default=True)
+ assert orig.clone().default is True
+ assert orig.clone(default=False).default is False
+
+
+class test_Data(ClassChecker):
+ """
+ Test the `ipalib.parameters.Data` class.
+ """
+ _cls = parameters.Data
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.Data.__init__` method.
+ """
+ o = self.cls('my_data')
+ assert o.type is NoneType
+ assert o.rules == tuple()
+ assert o.class_rules == tuple()
+ assert o.all_rules == tuple()
+ assert o.minlength is None
+ assert o.maxlength is None
+ assert o.length is None
+ assert not hasattr(o, 'pattern')
+
+ # Test mixing length with minlength or maxlength:
+ o = self.cls('my_data', length=5)
+ assert o.length == 5
+ permutations = [
+ dict(minlength=3),
+ dict(maxlength=7),
+ dict(minlength=3, maxlength=7),
+ ]
+ for kw in permutations:
+ o = self.cls('my_data', **kw)
+ for (key, value) in kw.iteritems():
+ assert getattr(o, key) == value
+ e = raises(ValueError, self.cls, 'my_data', length=5, **kw)
+ assert str(e) == \
+ "Data('my_data'): cannot mix length with minlength or maxlength"
+
+ # Test when minlength or maxlength are less than 1:
+ e = raises(ValueError, self.cls, 'my_data', minlength=0)
+ assert str(e) == "Data('my_data'): minlength must be >= 1; got 0"
+ e = raises(ValueError, self.cls, 'my_data', maxlength=0)
+ assert str(e) == "Data('my_data'): maxlength must be >= 1; got 0"
+
+ # Test when minlength > maxlength:
+ e = raises(ValueError, self.cls, 'my_data', minlength=22, maxlength=15)
+ assert str(e) == \
+ "Data('my_data'): minlength > maxlength (minlength=22, maxlength=15)"
+
+ # Test when minlength == maxlength
+ e = raises(ValueError, self.cls, 'my_data', minlength=7, maxlength=7)
+ assert str(e) == \
+ "Data('my_data'): minlength == maxlength; use length=7 instead"
+
+
+class test_Bytes(ClassChecker):
+ """
+ Test the `ipalib.parameters.Bytes` class.
+ """
+ _cls = parameters.Bytes
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.Bytes.__init__` method.
+ """
+ o = self.cls('my_bytes')
+ assert o.type is str
+ assert o.rules == tuple()
+ assert o.class_rules == tuple()
+ assert o.all_rules == tuple()
+ assert o.minlength is None
+ assert o.maxlength is None
+ assert o.length is None
+ assert o.pattern is None
+
+ # Test mixing length with minlength or maxlength:
+ o = self.cls('my_bytes', length=5)
+ assert o.length == 5
+ assert len(o.class_rules) == 1
+ assert len(o.rules) == 0
+ assert len(o.all_rules) == 1
+ permutations = [
+ dict(minlength=3),
+ dict(maxlength=7),
+ dict(minlength=3, maxlength=7),
+ ]
+ for kw in permutations:
+ o = self.cls('my_bytes', **kw)
+ assert len(o.class_rules) == len(kw)
+ assert len(o.rules) == 0
+ assert len(o.all_rules) == len(kw)
+ for (key, value) in kw.iteritems():
+ assert getattr(o, key) == value
+ e = raises(ValueError, self.cls, 'my_bytes', length=5, **kw)
+ assert str(e) == \
+ "Bytes('my_bytes'): cannot mix length with minlength or maxlength"
+
+ # Test when minlength or maxlength are less than 1:
+ e = raises(ValueError, self.cls, 'my_bytes', minlength=0)
+ assert str(e) == "Bytes('my_bytes'): minlength must be >= 1; got 0"
+ e = raises(ValueError, self.cls, 'my_bytes', maxlength=0)
+ assert str(e) == "Bytes('my_bytes'): maxlength must be >= 1; got 0"
+
+ # Test when minlength > maxlength:
+ e = raises(ValueError, self.cls, 'my_bytes', minlength=22, maxlength=15)
+ assert str(e) == \
+ "Bytes('my_bytes'): minlength > maxlength (minlength=22, maxlength=15)"
+
+ # Test when minlength == maxlength
+ e = raises(ValueError, self.cls, 'my_bytes', minlength=7, maxlength=7)
+ assert str(e) == \
+ "Bytes('my_bytes'): minlength == maxlength; use length=7 instead"
+
+ def test_rule_minlength(self):
+ """
+ Test the `ipalib.parameters.Bytes._rule_minlength` method.
+ """
+ o = self.cls('my_bytes', minlength=3)
+ assert o.minlength == 3
+ rule = o._rule_minlength
+ translation = u'minlength=%(minlength)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in ('abc', 'four', '12345'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in ('', 'a', '12'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(minlength=3)
+ )
+ assert dummy.message == 'must be at least %(minlength)d bytes'
+ assert dummy.called() is True
+ dummy.reset()
+
+ def test_rule_maxlength(self):
+ """
+ Test the `ipalib.parameters.Bytes._rule_maxlength` method.
+ """
+ o = self.cls('my_bytes', maxlength=4)
+ assert o.maxlength == 4
+ rule = o._rule_maxlength
+ translation = u'maxlength=%(maxlength)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in ('ab', '123', 'four'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in ('12345', 'sixsix'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(maxlength=4)
+ )
+ assert dummy.message == 'can be at most %(maxlength)d bytes'
+ assert dummy.called() is True
+ dummy.reset()
+
+ def test_rule_length(self):
+ """
+ Test the `ipalib.parameters.Bytes._rule_length` method.
+ """
+ o = self.cls('my_bytes', length=4)
+ assert o.length == 4
+ rule = o._rule_length
+ translation = u'length=%(length)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in ('1234', 'four'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in ('ab', '123', '12345', 'sixsix'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(length=4),
+ )
+ assert dummy.message == 'must be exactly %(length)d bytes'
+ assert dummy.called() is True
+ dummy.reset()
+
+
+class test_Str(ClassChecker):
+ """
+ Test the `ipalib.parameters.Str` class.
+ """
+ _cls = parameters.Str
+
+ def test_init(self):
+ """
+ Test the `ipalib.parameters.Str.__init__` method.
+ """
+ o = self.cls('my_str')
+ assert o.type is unicode
+ assert o.minlength is None
+ assert o.maxlength is None
+ assert o.length is None
+ assert o.pattern is None
+
+ def test_convert_scalar(self):
+ """
+ Test the `ipalib.parameters.Str._convert_scalar` method.
+ """
+ o = self.cls('my_str')
+ mthd = o._convert_scalar
+ for value in (u'Hello', 42, 1.2):
+ assert mthd(value) == unicode(value)
+ for value in [True, 'Hello', (u'Hello',), [42.3], dict(one=1)]:
+ e = raises(errors2.ConversionError, mthd, value)
+ assert e.name == 'my_str'
+ assert e.index is None
+ assert_equal(e.error, u'must be Unicode text')
+ e = raises(errors2.ConversionError, mthd, value, index=18)
+ assert e.name == 'my_str'
+ assert e.index == 18
+ assert_equal(e.error, u'must be Unicode text')
+
+ def test_rule_minlength(self):
+ """
+ Test the `ipalib.parameters.Str._rule_minlength` method.
+ """
+ o = self.cls('my_str', minlength=3)
+ assert o.minlength == 3
+ rule = o._rule_minlength
+ translation = u'minlength=%(minlength)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in (u'abc', u'four', u'12345'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in (u'', u'a', u'12'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(minlength=3)
+ )
+ assert dummy.message == 'must be at least %(minlength)d characters'
+ assert dummy.called() is True
+ dummy.reset()
+
+ def test_rule_maxlength(self):
+ """
+ Test the `ipalib.parameters.Str._rule_maxlength` method.
+ """
+ o = self.cls('my_str', maxlength=4)
+ assert o.maxlength == 4
+ rule = o._rule_maxlength
+ translation = u'maxlength=%(maxlength)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in (u'ab', u'123', u'four'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in (u'12345', u'sixsix'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(maxlength=4)
+ )
+ assert dummy.message == 'can be at most %(maxlength)d characters'
+ assert dummy.called() is True
+ dummy.reset()
+
+ def test_rule_length(self):
+ """
+ Test the `ipalib.parameters.Str._rule_length` method.
+ """
+ o = self.cls('my_str', length=4)
+ assert o.length == 4
+ rule = o._rule_length
+ translation = u'length=%(length)r'
+ dummy = dummy_ugettext(translation)
+ assert dummy.translation is translation
+
+ # Test with passing values:
+ for value in (u'1234', u'four'):
+ assert rule(dummy, value) is None
+ assert dummy.called() is False
+
+ # Test with failing values:
+ for value in (u'ab', u'123', u'12345', u'sixsix'):
+ assert_equal(
+ rule(dummy, value),
+ translation % dict(length=4),
+ )
+ assert dummy.message == 'must be exactly %(length)d characters'
+ assert dummy.called() is True
+ dummy.reset()
+
+
+def test_create_param():
+ """
+ Test the `ipalib.parameters.create_param` function.
+ """
+ f = parameters.create_param
+
+ # Test that Param instances are returned unchanged:
+ params = (
+ parameters.Param('one?'),
+ parameters.Int('two+'),
+ parameters.Str('three*'),
+ parameters.Bytes('four'),
+ )
+ for p in params:
+ assert f(p) is p
+
+ # Test that the spec creates an Str instance:
+ for spec in ('one?', 'two+', 'three*', 'four'):
+ (name, kw) = parameters.parse_param_spec(spec)
+ p = f(spec)
+ assert p.param_spec is spec
+ assert p.name == name
+ assert p.required is kw['required']
+ assert p.multivalue is kw['multivalue']
+
+ # Test that TypeError is raised when spec is neither a Param nor a str:
+ for spec in (u'one', 42, parameters.Param, parameters.Str):
+ e = raises(TypeError, f, spec)
+ assert str(e) == \
+ TYPE_ERROR % ('spec', (str, parameters.Param), spec, type(spec))
+
+
+def test_messages():
+ """
+ Test module level message in `ipalib.parameters`.
+ """
+ for name in dir(parameters):
+ if name.startswith('_'):
+ continue
+ attr = getattr(parameters, name)
+ if not (isclass(attr) and issubclass(attr, parameters.Param)):
+ continue
+ assert type(attr.type_error) is str
+ assert attr.type_error in parameters.__messages
diff --git a/tests/util.py b/tests/util.py
index 3033b82b..af4c2393 100644
--- a/tests/util.py
+++ b/tests/util.py
@@ -28,6 +28,7 @@ import tempfile
import shutil
import ipalib
from ipalib.plugable import Plugin
+from ipalib.request import context
@@ -202,6 +203,14 @@ class ClassChecker(object):
'get_subcls()'
)
+ def tearDown(self):
+ """
+ nose tear-down fixture.
+ """
+ for name in ('ugettext', 'ungettext'):
+ if hasattr(context, name):
+ delattr(context, name)
+
@@ -297,12 +306,24 @@ class dummy_ugettext(object):
assert type(self.translation) is unicode
def __call__(self, message):
- assert type(message) is str
assert self.__called is False
self.__called = True
+ assert type(message) is str
+ assert not hasattr(self, 'message')
self.message = message
+ assert type(self.translation) is unicode
return self.translation
+ def called(self):
+ return self.__called
+
+ def reset(self):
+ assert type(self.translation) is unicode
+ assert type(self.message) is str
+ del self.message
+ assert self.__called is True
+ self.__called = False
+
class dummy_ungettext(object):
__called = False