diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/__init__.py | 32 | ||||
-rw-r--r-- | ipalib/cli.py | 6 | ||||
-rw-r--r-- | ipalib/crud.py | 5 | ||||
-rw-r--r-- | ipalib/errors2.py | 24 | ||||
-rw-r--r-- | ipalib/frontend.py | 520 | ||||
-rw-r--r-- | ipalib/ipa_types.py | 189 | ||||
-rw-r--r-- | ipalib/parameters.py (renamed from ipalib/parameter.py) | 449 | ||||
-rw-r--r-- | ipalib/plugins/f_automount.py | 76 | ||||
-rw-r--r-- | ipalib/plugins/f_group.py | 38 | ||||
-rw-r--r-- | ipalib/plugins/f_host.py | 47 | ||||
-rw-r--r-- | ipalib/plugins/f_hostgroup.py | 34 | ||||
-rw-r--r-- | ipalib/plugins/f_passwd.py | 16 | ||||
-rw-r--r-- | ipalib/plugins/f_pwpolicy.py | 26 | ||||
-rw-r--r-- | ipalib/plugins/f_ra.py | 10 | ||||
-rw-r--r-- | ipalib/plugins/f_service.py | 25 | ||||
-rw-r--r-- | ipalib/plugins/f_user.py | 67 |
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): |