summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib')
-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
16 files changed, 643 insertions, 921 deletions
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):