2008-12-10 22:14:05 -06:00
|
|
|
# Authors:
|
|
|
|
# Jason Gerard DeRose <jderose@redhat.com>
|
|
|
|
#
|
|
|
|
# Copyright (C) 2008 Red Hat
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
2010-12-09 06:59:11 -06:00
|
|
|
# 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, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
2008-12-10 22:14:05 -06:00
|
|
|
#
|
|
|
|
# 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
|
2010-12-09 06:59:11 -06:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2008-12-10 22:14:05 -06:00
|
|
|
|
|
|
|
"""
|
|
|
|
Parameter system for command plugins.
|
2009-01-14 11:58:05 -06:00
|
|
|
|
2010-02-19 10:08:16 -06:00
|
|
|
A `Param` instance can be used to describe an argument or option that a command
|
|
|
|
takes, or an attribute that a command returns. The `Param` base class is not
|
|
|
|
used directly, but there are many subclasses for specific Python data types
|
|
|
|
(like `Str` or `Int`) and specific properties (like `Password`).
|
|
|
|
|
|
|
|
To create a `Param` instance, you must always provide the parameter *name*,
|
|
|
|
which should be the LDAP attribute name if the parameter describes the attribute
|
|
|
|
of an LDAP entry. For example, we could create an `Str` instance describing the user's last-name attribute like this:
|
|
|
|
|
|
|
|
>>> from ipalib import Str
|
|
|
|
>>> sn = Str('sn')
|
|
|
|
>>> sn.name
|
|
|
|
'sn'
|
|
|
|
|
|
|
|
When creating a `Param`, there are also a number of optional kwargs which
|
|
|
|
which can provide additional meta-data and functionality. For example, every
|
|
|
|
parameter has a *cli_name*, the name used on the command-line-interface. By
|
|
|
|
default the *cli_name* is the same as the *name*:
|
|
|
|
|
|
|
|
>>> sn.cli_name
|
|
|
|
'sn'
|
|
|
|
|
|
|
|
But often the LDAP attribute name isn't user friendly for the command-line, so
|
|
|
|
you can override this with the *cli_name* kwarg:
|
|
|
|
|
|
|
|
>>> sn = Str('sn', cli_name='last')
|
|
|
|
>>> sn.name
|
|
|
|
'sn'
|
|
|
|
>>> sn.cli_name
|
|
|
|
'last'
|
|
|
|
|
|
|
|
Note that the RPC interfaces (and the internal processing pipeline) always use
|
|
|
|
the parameter *name*, regardless of what the *cli_name* might be.
|
|
|
|
|
|
|
|
A `Param` also has two translatable kwargs: *label* and *doc*. These must both
|
|
|
|
be `Gettext` instances. They both default to a place-holder `FixMe` instance,
|
|
|
|
a subclass of `Gettext` used to mark a missing translatable string:
|
|
|
|
|
|
|
|
>>> sn.label
|
|
|
|
FixMe('sn')
|
|
|
|
>>> sn.doc
|
|
|
|
FixMe('sn')
|
|
|
|
|
|
|
|
The *label* is a short phrase describing the parameter. It's used on the CLI
|
|
|
|
when interactively prompting for values, and as a label for form inputs in the
|
|
|
|
web-UI. The *label* should start with an initial capital. For example:
|
|
|
|
|
|
|
|
>>> from ipalib import _
|
|
|
|
>>> sn = Str('sn',
|
|
|
|
... cli_name='last',
|
|
|
|
... label=_('Last name'),
|
|
|
|
... )
|
|
|
|
>>> sn.label
|
2010-03-08 21:42:26 -06:00
|
|
|
Gettext('Last name', domain='ipa', localedir=None)
|
2010-02-19 10:08:16 -06:00
|
|
|
|
|
|
|
The *doc* is a longer description of the parameter. It's used on the CLI when
|
|
|
|
displaying the help information for a command, and as extra instruction for a
|
|
|
|
form input on the web-UI. By default the *doc* is the same as the *label*:
|
|
|
|
|
|
|
|
>>> sn.doc
|
2010-03-08 21:42:26 -06:00
|
|
|
Gettext('Last name', domain='ipa', localedir=None)
|
2010-02-19 10:08:16 -06:00
|
|
|
|
|
|
|
But you can override this with the *doc* kwarg. Like the *label*, the *doc*
|
|
|
|
should also start with an initial capital and should not end with any
|
|
|
|
punctuation. For example:
|
|
|
|
|
|
|
|
>>> sn = Str('sn',
|
|
|
|
... cli_name='last',
|
|
|
|
... label=_('Last name'),
|
|
|
|
... doc=_("The user's last name"),
|
|
|
|
... )
|
|
|
|
>>> sn.doc
|
2010-03-08 21:42:26 -06:00
|
|
|
Gettext("The user's last name", domain='ipa', localedir=None)
|
2010-02-19 10:08:16 -06:00
|
|
|
|
|
|
|
Demonstration aside, you should always provide at least the *label* so the
|
|
|
|
various UIs are translatable. Only provide the *doc* if the parameter needs
|
|
|
|
a more detailed description for clarity.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2009-02-06 13:36:49 -06:00
|
|
|
import re
|
2008-12-11 23:39:50 -06:00
|
|
|
from types import NoneType
|
2009-01-02 03:22:48 -06:00
|
|
|
from util import make_repr
|
2011-02-02 14:37:14 -06:00
|
|
|
from text import _ as ugettext
|
2008-12-10 22:14:05 -06:00
|
|
|
from plugable import ReadOnly, lock, check_name
|
2009-04-23 07:51:59 -05:00
|
|
|
from errors import ConversionError, RequirementError, ValidationError
|
2009-10-16 03:22:39 -05:00
|
|
|
from errors import PasswordMismatch
|
2008-12-11 19:07:54 -06:00
|
|
|
from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR
|
2010-02-19 10:08:16 -06:00
|
|
|
from text import Gettext, FixMe
|
2009-03-18 14:44:53 -05:00
|
|
|
import csv
|
2011-07-19 21:10:22 -05:00
|
|
|
from xmlrpclib import MAXINT, MININT
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2009-02-06 13:36:49 -06:00
|
|
|
|
2008-12-11 21:30:59 -06:00
|
|
|
class DefaultFrom(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'
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
If you do not explicitly provide keys when you create a `DefaultFrom`
|
2008-12-11 21:30:59 -06:00
|
|
|
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:
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
>>> login.callback # doctest:+ELLIPSIS
|
2008-12-11 21:30:59 -06:00
|
|
|
<function <lambda> at 0x...>
|
2009-01-02 03:22:48 -06:00
|
|
|
>>> login.callback.func_code.co_varnames # The keys
|
2008-12-11 21:30:59 -06:00
|
|
|
('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')
|
2009-01-02 03:22:48 -06:00
|
|
|
>>> login2.callback.func_code.co_varnames # Not the keys
|
2008-12-11 21:30:59 -06:00
|
|
|
('a', 'b')
|
|
|
|
>>> login2(first='John', last='Doe')
|
|
|
|
'JDoe'
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
If any keys are missing when calling your `DefaultFrom` instance, your
|
|
|
|
callback is not called and ``None`` is returned. For example:
|
2008-12-11 21:30:59 -06:00
|
|
|
|
|
|
|
>>> 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.
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
Of course, the callback need not be a ``lambda`` expression. This third
|
2008-12-11 21:30:59 -06:00
|
|
|
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):
|
2008-12-18 03:08:41 -06:00
|
|
|
raise TypeError(
|
|
|
|
CALLABLE_ERROR % ('callback', callback, type(callback))
|
|
|
|
)
|
2008-12-11 21:30:59 -06:00
|
|
|
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:
|
2008-12-18 03:08:41 -06:00
|
|
|
raise TypeError(
|
|
|
|
TYPE_ERROR % ('keys', str, key, type(key))
|
|
|
|
)
|
2008-12-11 21:30:59 -06:00
|
|
|
lock(self)
|
|
|
|
|
2009-10-13 12:28:00 -05:00
|
|
|
def __repr__(self):
|
|
|
|
args = (self.callback.__name__,) + tuple(repr(k) for k in self.keys)
|
|
|
|
return '%s(%s)' % (
|
|
|
|
self.__class__.__name__,
|
|
|
|
', '.join(args)
|
|
|
|
)
|
|
|
|
|
2008-12-11 21:30:59 -06:00
|
|
|
def __call__(self, **kw):
|
|
|
|
"""
|
2009-01-02 18:27:44 -06:00
|
|
|
Call the callback if all keys are present.
|
|
|
|
|
|
|
|
If all keys are present, the callback is called and its return value is
|
|
|
|
returned. If any keys are missing, ``None`` is returned.
|
2008-12-11 21:30:59 -06:00
|
|
|
|
|
|
|
: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
|
|
|
|
|
|
|
|
|
2008-12-18 02:45:13 -06:00
|
|
|
def parse_param_spec(spec):
|
|
|
|
"""
|
2009-01-13 01:27:06 -06:00
|
|
|
Parse shorthand ``spec`` into to ``(name, kw)``.
|
2008-12-18 02:45:13 -06:00
|
|
|
|
2009-01-13 01:27:06 -06:00
|
|
|
The ``spec`` string determines the parameter name, whether the parameter is
|
|
|
|
required, and whether the parameter is multivalue according the following
|
2008-12-18 02:45:13 -06:00
|
|
|
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:
|
2008-12-18 02:57:39 -06:00
|
|
|
raise TypeError(
|
|
|
|
TYPE_ERROR % ('spec', str, spec, type(spec))
|
|
|
|
)
|
2008-12-18 02:45:13 -06:00
|
|
|
_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))
|
|
|
|
|
|
|
|
|
2009-01-13 03:17:16 -06:00
|
|
|
__messages = set()
|
|
|
|
|
|
|
|
def _(message):
|
|
|
|
__messages.add(message)
|
|
|
|
return message
|
|
|
|
|
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
class Param(ReadOnly):
|
|
|
|
"""
|
2008-12-11 23:39:50 -06:00
|
|
|
Base class for all parameters.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2008-12-12 04:38:02 -06:00
|
|
|
# This is a dummy type so that most of the functionality of Param can be
|
2008-12-12 05:48:25 -06:00
|
|
|
# unit tested directly without always creating a subclass; however, a real
|
|
|
|
# (direct) subclass must *always* override this class attribute:
|
|
|
|
type = NoneType # Ouch, this wont be very useful in the real world!
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2009-01-13 03:17:16 -06:00
|
|
|
# Subclasses should override this with something more specific:
|
|
|
|
type_error = _('incorrect type')
|
|
|
|
|
2009-11-04 08:41:48 -06:00
|
|
|
# _convert_scalar operates only on scalar values
|
|
|
|
scalar_error = _('Only one value is allowed')
|
|
|
|
|
2008-12-11 23:39:50 -06:00
|
|
|
kwargs = (
|
|
|
|
('cli_name', str, None),
|
2009-06-10 14:07:24 -05:00
|
|
|
('cli_short_name', str, None),
|
2011-07-12 11:01:25 -05:00
|
|
|
('label', (basestring, Gettext), None),
|
2011-02-23 15:47:49 -06:00
|
|
|
('doc', (basestring, Gettext), None),
|
2008-12-11 23:39:50 -06:00
|
|
|
('required', bool, True),
|
|
|
|
('multivalue', bool, False),
|
|
|
|
('primary_key', bool, False),
|
|
|
|
('normalizer', callable, None),
|
2009-01-12 23:48:04 -06:00
|
|
|
('default_from', DefaultFrom, None),
|
2009-01-12 17:14:46 -06:00
|
|
|
('create_default', callable, None),
|
2009-01-13 21:27:19 -06:00
|
|
|
('autofill', bool, False),
|
2009-01-14 15:04:05 -06:00
|
|
|
('query', bool, False),
|
2009-01-21 18:19:39 -06:00
|
|
|
('attribute', bool, False),
|
2009-05-13 02:04:35 -05:00
|
|
|
('include', frozenset, None),
|
|
|
|
('exclude', frozenset, None),
|
2008-12-11 23:39:50 -06:00
|
|
|
('flags', frozenset, frozenset()),
|
2010-09-16 09:28:07 -05:00
|
|
|
('hint', (str, Gettext), None),
|
2010-12-02 15:29:26 -06:00
|
|
|
('alwaysask', bool, False),
|
2008-12-11 23:39:50 -06:00
|
|
|
|
|
|
|
# The 'default' kwarg gets appended in Param.__init__():
|
|
|
|
# ('default', self.type, None),
|
2008-12-10 22:14:05 -06:00
|
|
|
)
|
|
|
|
|
2008-12-12 05:48:25 -06:00
|
|
|
def __init__(self, name, *rules, **kw):
|
2008-12-12 04:38:02 -06:00
|
|
|
# We keep these values to use in __repr__():
|
2008-12-10 22:14:05 -06:00
|
|
|
self.param_spec = name
|
2008-12-11 23:39:50 -06:00
|
|
|
self.__kw = dict(kw)
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2009-01-22 20:40:02 -06:00
|
|
|
if isinstance(self, Password):
|
|
|
|
self.password = True
|
|
|
|
else:
|
|
|
|
self.password = False
|
|
|
|
|
2008-12-12 04:38:02 -06:00
|
|
|
# Merge in kw from parse_param_spec():
|
2009-06-01 11:59:58 -05:00
|
|
|
(name, kw_from_spec) = parse_param_spec(name)
|
|
|
|
if not 'required' in kw:
|
|
|
|
kw['required'] = kw_from_spec['required']
|
|
|
|
if not 'multivalue' in kw:
|
|
|
|
kw['multivalue'] = kw_from_spec['multivalue']
|
2008-12-10 22:14:05 -06:00
|
|
|
self.name = check_name(name)
|
2008-12-17 19:32:46 -06:00
|
|
|
self.nice = '%s(%r)' % (self.__class__.__name__, self.param_spec)
|
2008-12-12 04:38:02 -06:00
|
|
|
|
|
|
|
# Add 'default' to self.kwargs and makes sure no unknown kw were given:
|
|
|
|
assert type(self.type) is type
|
2009-02-23 11:55:36 -06:00
|
|
|
if kw.get('multivalue', True):
|
|
|
|
self.kwargs += (('default', tuple, None),)
|
|
|
|
else:
|
|
|
|
self.kwargs += (('default', self.type, None),)
|
2008-12-12 04:13:58 -06:00
|
|
|
if not set(t[0] for t in self.kwargs).issuperset(self.__kw):
|
|
|
|
extra = set(kw) - set(t[0] for t in self.kwargs)
|
|
|
|
raise TypeError(
|
2008-12-12 04:38:02 -06:00
|
|
|
'%s: takes no such kwargs: %s' % (self.nice,
|
2008-12-12 04:13:58 -06:00
|
|
|
', '.join(repr(k) for k in sorted(extra))
|
|
|
|
)
|
|
|
|
)
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2009-12-09 10:09:53 -06:00
|
|
|
# Merge in default for 'cli_name', label, doc if not given:
|
|
|
|
if kw.get('cli_name') is None:
|
2008-12-11 23:39:50 -06:00
|
|
|
kw['cli_name'] = self.name
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2009-12-09 10:09:53 -06:00
|
|
|
if kw.get('label') is None:
|
2010-02-19 10:08:16 -06:00
|
|
|
kw['label'] = FixMe(self.name)
|
2009-12-09 10:09:53 -06:00
|
|
|
|
|
|
|
if kw.get('doc') is None:
|
|
|
|
kw['doc'] = kw['label']
|
|
|
|
|
2008-12-12 04:38:02 -06:00
|
|
|
# Wrap 'default_from' in a DefaultFrom if not already:
|
2008-12-11 23:39:50 -06:00
|
|
|
df = kw.get('default_from', None)
|
2008-12-11 21:30:59 -06:00
|
|
|
if callable(df) and not isinstance(df, DefaultFrom):
|
2008-12-11 23:39:50 -06:00
|
|
|
kw['default_from'] = DefaultFrom(df)
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2008-12-12 05:48:25 -06:00
|
|
|
# We keep this copy with merged values also to use when cloning:
|
2008-12-11 23:39:50 -06:00
|
|
|
self.__clonekw = kw
|
2008-12-12 04:38:02 -06:00
|
|
|
|
2008-12-12 05:48:25 -06:00
|
|
|
# Perform type validation on kw, add in class rules:
|
|
|
|
class_rules = []
|
2008-12-11 23:39:50 -06:00
|
|
|
for (key, kind, default) in self.kwargs:
|
|
|
|
value = kw.get(key, default)
|
|
|
|
if value is not None:
|
2008-12-12 04:38:02 -06:00
|
|
|
if kind is frozenset:
|
|
|
|
if type(value) in (list, tuple):
|
|
|
|
value = frozenset(value)
|
|
|
|
elif type(value) is str:
|
|
|
|
value = frozenset([value])
|
2008-12-11 19:07:54 -06:00
|
|
|
if (
|
2011-02-23 15:47:49 -06:00
|
|
|
type(kind) is type and not isinstance(value, kind)
|
2008-12-11 21:30:59 -06:00
|
|
|
or
|
2008-12-11 19:07:54 -06:00
|
|
|
type(kind) is tuple and not isinstance(value, kind)
|
|
|
|
):
|
|
|
|
raise TypeError(
|
|
|
|
TYPE_ERROR % (key, kind, value, type(value))
|
|
|
|
)
|
|
|
|
elif kind is callable and not callable(value):
|
|
|
|
raise TypeError(
|
|
|
|
CALLABLE_ERROR % (key, value, type(value))
|
|
|
|
)
|
|
|
|
if hasattr(self, key):
|
|
|
|
raise ValueError('kwarg %r conflicts with attribute on %s' % (
|
|
|
|
key, self.__class__.__name__)
|
|
|
|
)
|
|
|
|
setattr(self, key, value)
|
2008-12-18 00:30:29 -06:00
|
|
|
rule_name = '_rule_%s' % key
|
2008-12-12 05:48:25 -06:00
|
|
|
if value is not None and hasattr(self, rule_name):
|
|
|
|
class_rules.append(getattr(self, rule_name))
|
2008-12-11 21:30:59 -06:00
|
|
|
check_name(self.cli_name)
|
2008-12-12 05:48:25 -06:00
|
|
|
|
2009-01-12 23:48:04 -06:00
|
|
|
# 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
|
|
|
|
|
2009-05-13 02:04:35 -05:00
|
|
|
# Check that only 'include' or 'exclude' was provided:
|
|
|
|
if None not in (self.include, self.exclude):
|
|
|
|
raise ValueError(
|
|
|
|
'%s: cannot have both %s=%r and %s=%r' % (
|
|
|
|
self.nice,
|
|
|
|
'include', self.include,
|
|
|
|
'exclude', self.exclude,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2008-12-12 05:48:25 -06:00
|
|
|
# Check that all the rules are callable
|
2008-12-17 19:32:46 -06:00
|
|
|
self.class_rules = tuple(class_rules)
|
|
|
|
self.rules = rules
|
2011-06-24 13:32:57 -05:00
|
|
|
if self.query:
|
|
|
|
self.all_rules = self.class_rules
|
|
|
|
else:
|
|
|
|
self.all_rules = self.class_rules + self.rules
|
2008-12-17 19:32:46 -06:00
|
|
|
for rule in self.all_rules:
|
2008-12-12 05:48:25 -06:00
|
|
|
if not callable(rule):
|
|
|
|
raise TypeError(
|
|
|
|
'%s: rules must be callable; got %r' % (self.nice, rule)
|
|
|
|
)
|
|
|
|
|
2009-08-04 01:21:26 -05:00
|
|
|
# Check that cli_short_name is only 1 character long:
|
|
|
|
if not (self.cli_short_name is None or len(self.cli_short_name) == 1):
|
|
|
|
raise ValueError(
|
|
|
|
'%s: cli_short_name can only be a single character: %s' % (
|
|
|
|
self.nice, self.cli_short_name)
|
|
|
|
)
|
|
|
|
|
2008-12-12 05:48:25 -06:00
|
|
|
# And we're done.
|
2008-12-10 22:14:05 -06:00
|
|
|
lock(self)
|
|
|
|
|
2008-12-18 12:21:12 -06:00
|
|
|
def __repr__(self):
|
|
|
|
"""
|
|
|
|
Return an expresion that could construct this `Param` instance.
|
|
|
|
"""
|
2009-05-13 02:04:35 -05:00
|
|
|
return '%s(%s)' % (
|
2008-12-18 12:21:12 -06:00
|
|
|
self.__class__.__name__,
|
2009-05-13 02:04:35 -05:00
|
|
|
', '.join(self.__repr_iter())
|
2008-12-18 12:21:12 -06:00
|
|
|
)
|
|
|
|
|
2009-05-13 02:04:35 -05:00
|
|
|
def __repr_iter(self):
|
|
|
|
yield repr(self.param_spec)
|
|
|
|
for rule in self.rules:
|
|
|
|
yield rule.__name__
|
|
|
|
for key in sorted(self.__kw):
|
2009-10-13 12:28:00 -05:00
|
|
|
value = self.__kw[key]
|
|
|
|
if callable(value) and hasattr(value, '__name__'):
|
|
|
|
value = value.__name__
|
|
|
|
else:
|
|
|
|
value = repr(value)
|
|
|
|
yield '%s=%s' % (key, value)
|
2009-05-13 02:04:35 -05:00
|
|
|
|
2009-01-13 21:27:19 -06:00
|
|
|
def __call__(self, value, **kw):
|
|
|
|
"""
|
|
|
|
One stop shopping.
|
|
|
|
"""
|
|
|
|
if value in NULLS:
|
|
|
|
value = self.get_default(**kw)
|
|
|
|
else:
|
|
|
|
value = self.convert(self.normalize(value))
|
2010-10-11 21:27:57 -05:00
|
|
|
if hasattr(self, 'env'):
|
2011-04-07 09:53:52 -05:00
|
|
|
self.validate(value, self.env.context) #pylint: disable=E1101
|
2010-10-11 21:27:57 -05:00
|
|
|
else:
|
|
|
|
self.validate(value)
|
2009-01-13 21:27:19 -06:00
|
|
|
return value
|
|
|
|
|
2009-10-13 12:28:00 -05:00
|
|
|
def kw(self):
|
|
|
|
"""
|
|
|
|
Iterate through ``(key,value)`` for all kwargs passed to constructor.
|
|
|
|
"""
|
|
|
|
for key in sorted(self.__kw):
|
|
|
|
value = self.__kw[key]
|
|
|
|
if callable(value) and hasattr(value, '__name__'):
|
|
|
|
value = value.__name__
|
|
|
|
yield (key, value)
|
|
|
|
|
2009-05-13 02:04:35 -05:00
|
|
|
def use_in_context(self, env):
|
|
|
|
"""
|
2009-05-20 16:19:09 -05:00
|
|
|
Return ``True`` if this parameter should be used in ``env.context``.
|
2009-05-13 02:04:35 -05:00
|
|
|
|
2009-05-20 16:19:09 -05:00
|
|
|
If a parameter is created with niether the ``include`` nor the
|
|
|
|
``exclude`` kwarg, this method will always return ``True``. For
|
|
|
|
example:
|
2009-05-13 02:04:35 -05:00
|
|
|
|
|
|
|
>>> from ipalib.config import Env
|
2009-05-20 16:19:09 -05:00
|
|
|
>>> param = Param('my_param')
|
|
|
|
>>> param.use_in_context(Env(context='foo'))
|
2009-05-13 02:04:35 -05:00
|
|
|
True
|
2009-05-20 16:19:09 -05:00
|
|
|
>>> param.use_in_context(Env(context='bar'))
|
|
|
|
True
|
|
|
|
|
|
|
|
If a parameter is created with an ``include`` kwarg, this method will
|
|
|
|
only return ``True`` if ``env.context`` is in ``include``. For example:
|
|
|
|
|
|
|
|
>>> param = Param('my_param', include=['foo', 'whatever'])
|
|
|
|
>>> param.include
|
|
|
|
frozenset(['foo', 'whatever'])
|
|
|
|
>>> param.use_in_context(Env(context='foo'))
|
|
|
|
True
|
|
|
|
>>> param.use_in_context(Env(context='bar'))
|
|
|
|
False
|
|
|
|
|
|
|
|
If a paremeter is created with an ``exclude`` kwarg, this method will
|
|
|
|
only return ``True`` if ``env.context`` is not in ``exclude``. For
|
|
|
|
example:
|
|
|
|
|
|
|
|
>>> param = Param('my_param', exclude=['foo', 'whatever'])
|
|
|
|
>>> param.exclude
|
|
|
|
frozenset(['foo', 'whatever'])
|
|
|
|
>>> param.use_in_context(Env(context='foo'))
|
2009-05-13 02:04:35 -05:00
|
|
|
False
|
2009-05-20 16:19:09 -05:00
|
|
|
>>> param.use_in_context(Env(context='bar'))
|
|
|
|
True
|
|
|
|
|
|
|
|
Note that the ``include`` and ``exclude`` kwargs are mutually exclusive
|
|
|
|
and that at most one can be suppelied to `Param.__init__()`. For
|
|
|
|
example:
|
|
|
|
|
|
|
|
>>> param = Param('nope', include=['foo'], exclude=['bar'])
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValueError: Param('nope'): cannot have both include=frozenset(['foo']) and exclude=frozenset(['bar'])
|
2009-05-13 02:04:35 -05:00
|
|
|
|
2009-05-20 16:19:09 -05:00
|
|
|
So that subclasses can add additional logic based on other environment
|
|
|
|
variables, the entire `config.Env` instance is passed in rather than
|
|
|
|
just the value of ``env.context``.
|
2009-05-13 02:04:35 -05:00
|
|
|
"""
|
|
|
|
if self.include is not None:
|
|
|
|
return (env.context in self.include)
|
|
|
|
if self.exclude is not None:
|
|
|
|
return (env.context not in self.exclude)
|
|
|
|
return True
|
|
|
|
|
2009-01-22 19:48:21 -06:00
|
|
|
def safe_value(self, value):
|
2009-01-23 13:20:32 -06:00
|
|
|
"""
|
|
|
|
Return a value safe for logging.
|
|
|
|
|
|
|
|
This is used so that passwords don't get logged. If this is a
|
|
|
|
`Password` instance and ``value`` is not ``None``, a constant
|
|
|
|
``u'********'`` is returned. For example:
|
|
|
|
|
|
|
|
>>> p = Password('my_password')
|
|
|
|
>>> p.safe_value(u'This is my password')
|
|
|
|
u'********'
|
|
|
|
>>> p.safe_value(None) is None
|
|
|
|
True
|
|
|
|
|
|
|
|
If this is not a `Password` instance, ``value`` is returned unchanged.
|
|
|
|
For example:
|
|
|
|
|
|
|
|
>>> s = Str('my_str')
|
|
|
|
>>> s.safe_value(u'Some arbitrary value')
|
|
|
|
u'Some arbitrary value'
|
|
|
|
"""
|
2009-01-22 20:40:02 -06:00
|
|
|
if self.password and value is not None:
|
2009-01-22 19:48:21 -06:00
|
|
|
return u'********'
|
|
|
|
return value
|
|
|
|
|
2009-01-13 20:49:23 -06:00
|
|
|
def clone(self, **overrides):
|
|
|
|
"""
|
|
|
|
Return a new `Param` instance similar to this one.
|
|
|
|
"""
|
2010-12-14 04:06:26 -06:00
|
|
|
return self.clone_rename(self.name, **overrides)
|
|
|
|
|
|
|
|
def clone_rename(self, name, **overrides):
|
|
|
|
"""
|
|
|
|
Return a new `Param` instance similar to this one, but named differently
|
|
|
|
"""
|
2011-01-05 09:07:23 -06:00
|
|
|
return self.clone_retype(name, self.__class__, **overrides)
|
|
|
|
|
|
|
|
def clone_retype(self, name, klass, **overrides):
|
|
|
|
"""
|
|
|
|
Return a new `Param` instance similar to this one, but of a different type
|
|
|
|
"""
|
2009-01-13 20:49:23 -06:00
|
|
|
kw = dict(self.__clonekw)
|
|
|
|
kw.update(overrides)
|
2011-01-05 09:07:23 -06:00
|
|
|
return klass(name, *self.rules, **kw)
|
2009-01-13 20:49:23 -06:00
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
def normalize(self, value):
|
|
|
|
"""
|
2008-12-11 21:30:59 -06:00
|
|
|
Normalize ``value`` using normalizer callback.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
2008-12-12 04:13:58 -06:00
|
|
|
>>> param = Param('telephone',
|
2008-12-11 21:30:59 -06:00
|
|
|
... normalizer=lambda value: value.replace('.', '-')
|
|
|
|
... )
|
|
|
|
>>> param.normalize(u'800.123.4567')
|
|
|
|
u'800-123-4567'
|
|
|
|
|
|
|
|
If this `Param` instance was created with a normalizer callback and
|
|
|
|
``value`` is a unicode instance, the normalizer callback is called and
|
|
|
|
*its* return value is returned.
|
|
|
|
|
|
|
|
On the other hand, if this `Param` instance was *not* created with a
|
|
|
|
normalizer callback, if ``value`` is *not* a unicode instance, or if an
|
|
|
|
exception is caught when calling the normalizer callback, ``value`` is
|
|
|
|
returned unchanged.
|
|
|
|
|
|
|
|
:param value: A proposed value for this parameter.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2008-12-11 21:30:59 -06:00
|
|
|
if self.normalizer is None:
|
2008-12-10 22:14:05 -06:00
|
|
|
return value
|
|
|
|
if self.multivalue:
|
|
|
|
if type(value) in (tuple, list):
|
|
|
|
return tuple(
|
2008-12-18 01:02:38 -06:00
|
|
|
self._normalize_scalar(v) for v in value
|
2008-12-10 22:14:05 -06:00
|
|
|
)
|
2008-12-18 00:32:58 -06:00
|
|
|
return (self._normalize_scalar(value),) # Return a tuple
|
|
|
|
return self._normalize_scalar(value)
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2008-12-18 00:32:58 -06:00
|
|
|
def _normalize_scalar(self, value):
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
Normalize a scalar value.
|
|
|
|
|
2008-12-11 23:39:50 -06:00
|
|
|
This method is called once for each value in a multivalue.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
if type(value) is not unicode:
|
|
|
|
return value
|
|
|
|
try:
|
2008-12-11 21:30:59 -06:00
|
|
|
return self.normalizer(value)
|
2008-12-10 22:14:05 -06:00
|
|
|
except StandardError:
|
|
|
|
return value
|
|
|
|
|
|
|
|
def convert(self, value):
|
2008-12-18 02:18:17 -06:00
|
|
|
"""
|
|
|
|
Convert ``value`` to the Python type required by this parameter.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
>>> scalar = Str('my_scalar')
|
|
|
|
>>> scalar.type
|
|
|
|
<type 'unicode'>
|
|
|
|
>>> scalar.convert(43.2)
|
|
|
|
u'43.2'
|
|
|
|
|
|
|
|
(Note that `Str` is a subclass of `Param`.)
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
All values in `constants.NULLS` will be converted to ``None``. For
|
2008-12-18 02:18:17 -06:00
|
|
|
example:
|
|
|
|
|
|
|
|
>>> scalar.convert(u'') is None # An empty string
|
|
|
|
True
|
|
|
|
>>> scalar.convert([]) is None # An empty list
|
|
|
|
True
|
|
|
|
|
|
|
|
Likewise, values in `constants.NULLS` will be filtered out of a
|
|
|
|
multivalue parameter. For example:
|
|
|
|
|
|
|
|
>>> multi = Str('my_multi', multivalue=True)
|
2009-01-13 19:29:45 -06:00
|
|
|
>>> multi.convert([1.5, '', 17, None, u'Hello'])
|
|
|
|
(u'1.5', u'17', u'Hello')
|
2008-12-18 02:18:17 -06:00
|
|
|
>>> multi.convert([None, u'']) is None # Filters to an empty list
|
|
|
|
True
|
|
|
|
|
2009-01-02 03:22:48 -06:00
|
|
|
Lastly, multivalue parameters will always return a ``tuple`` (assuming
|
|
|
|
they don't return ``None`` as in the last example above). For example:
|
2008-12-18 02:18:17 -06:00
|
|
|
|
|
|
|
>>> multi.convert(42) # Called with a scalar value
|
|
|
|
(u'42',)
|
2009-01-13 19:29:45 -06:00
|
|
|
>>> multi.convert([0, 1]) # Called with a list value
|
|
|
|
(u'0', u'1')
|
2008-12-18 02:18:17 -06:00
|
|
|
|
|
|
|
Note that how values are converted (and from what types they will be
|
|
|
|
converted) completely depends upon how a subclass implements its
|
|
|
|
`Param._convert_scalar()` method. For example, see
|
|
|
|
`Str._convert_scalar()`.
|
|
|
|
|
|
|
|
:param value: A proposed value for this parameter.
|
|
|
|
"""
|
2008-12-10 22:14:05 -06:00
|
|
|
if value in NULLS:
|
|
|
|
return
|
|
|
|
if self.multivalue:
|
2008-12-18 01:02:38 -06:00
|
|
|
if type(value) not in (tuple, list):
|
|
|
|
value = (value,)
|
2008-12-18 02:18:17 -06:00
|
|
|
values = tuple(
|
|
|
|
self._convert_scalar(v, i) for (i, v) in filter(
|
2009-01-13 19:29:45 -06:00
|
|
|
lambda iv: iv[1] not in NULLS, enumerate(value)
|
2008-12-18 02:18:17 -06:00
|
|
|
)
|
2008-12-18 01:02:38 -06:00
|
|
|
)
|
|
|
|
if len(values) == 0:
|
|
|
|
return
|
2008-12-18 02:27:03 -06:00
|
|
|
return values
|
2008-12-18 01:02:38 -06:00
|
|
|
return self._convert_scalar(value)
|
2008-12-10 22:14:05 -06:00
|
|
|
|
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
"""
|
2009-01-13 03:17:16 -06:00
|
|
|
Convert a single scalar value.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2009-01-13 03:17:16 -06:00
|
|
|
if type(value) is self.type:
|
|
|
|
return value
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.type_error),
|
2008-12-10 22:14:05 -06:00
|
|
|
)
|
|
|
|
|
2010-10-11 21:27:57 -05:00
|
|
|
def validate(self, value, context=None):
|
2009-01-03 03:35:36 -06:00
|
|
|
"""
|
|
|
|
Check validity of ``value``.
|
|
|
|
|
|
|
|
:param value: A proposed value for this parameter.
|
2010-10-11 21:27:57 -05:00
|
|
|
:param context: The context we are running in.
|
2009-01-03 03:35:36 -06:00
|
|
|
"""
|
2009-01-12 17:14:46 -06:00
|
|
|
if value is None:
|
|
|
|
if self.required:
|
2010-10-11 21:27:57 -05:00
|
|
|
if context == 'cli':
|
|
|
|
raise RequirementError(name=self.cli_name)
|
|
|
|
else:
|
|
|
|
raise RequirementError(name=self.name)
|
2009-01-12 17:14:46 -06:00
|
|
|
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:
|
2009-08-25 07:37:41 -05:00
|
|
|
raise ValidationError(name=self.name,
|
|
|
|
error='need a %r; got %r (a %r)' % (
|
|
|
|
self.type, value, type(value)
|
|
|
|
)
|
2009-01-12 17:14:46 -06:00
|
|
|
)
|
|
|
|
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:
|
2010-12-21 11:14:38 -06:00
|
|
|
name = self.cli_name
|
|
|
|
if not name:
|
|
|
|
name = self.name
|
2009-01-14 14:51:37 -06:00
|
|
|
raise ValidationError(
|
2010-12-21 11:14:38 -06:00
|
|
|
name=name,
|
2009-01-14 14:51:37 -06:00
|
|
|
value=value,
|
|
|
|
index=index,
|
|
|
|
error=error,
|
|
|
|
rule=rule,
|
|
|
|
)
|
2009-01-03 03:35:36 -06:00
|
|
|
|
2009-01-12 23:48:04 -06:00
|
|
|
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
|
|
|
|
|
2010-08-10 15:40:00 -05:00
|
|
|
def __json__(self):
|
|
|
|
json_dict = {}
|
|
|
|
for (a, k, d) in self.kwargs:
|
|
|
|
if k in (callable, DefaultFrom):
|
|
|
|
continue
|
|
|
|
elif isinstance(getattr(self, a), frozenset):
|
|
|
|
json_dict[a] = [k for k in getattr(self, a, [])]
|
|
|
|
else:
|
|
|
|
json_dict[a] = getattr(self, a, '')
|
|
|
|
json_dict['class'] = self.__class__.__name__
|
|
|
|
json_dict['name'] = self.name
|
|
|
|
json_dict['type'] = self.type.__name__
|
|
|
|
return json_dict
|
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
|
|
|
|
class Bool(Param):
|
|
|
|
"""
|
2009-01-13 19:29:45 -06:00
|
|
|
A parameter for boolean values (stored in the ``bool`` type).
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2009-01-13 21:27:19 -06:00
|
|
|
type = bool
|
2009-01-14 11:17:39 -06:00
|
|
|
type_error = _('must be True or False')
|
2009-01-13 21:27:19 -06:00
|
|
|
|
2009-10-13 12:28:00 -05:00
|
|
|
# FIXME: This my quick hack to get some UI stuff working, change these defaults
|
|
|
|
# --jderose 2009-08-28
|
|
|
|
kwargs = Param.kwargs + (
|
2011-07-07 10:58:18 -05:00
|
|
|
('truths', frozenset, frozenset([1, u'1', True, u'true', u'TRUE'])),
|
|
|
|
('falsehoods', frozenset, frozenset([0, u'0', False, u'false', u'FALSE'])),
|
2009-10-13 12:28:00 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
"""
|
|
|
|
Convert a single scalar value.
|
|
|
|
"""
|
|
|
|
if type(value) is self.type:
|
|
|
|
return value
|
2009-11-26 08:53:07 -06:00
|
|
|
if isinstance(value, basestring):
|
|
|
|
value = value.lower()
|
2009-10-13 12:28:00 -05:00
|
|
|
if value in self.truths:
|
|
|
|
return True
|
|
|
|
if value in self.falsehoods:
|
|
|
|
return False
|
2009-11-04 08:41:48 -06:00
|
|
|
if type(value) in (tuple, list):
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.scalar_error))
|
2009-10-13 12:28:00 -05:00
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.type_error),
|
|
|
|
)
|
|
|
|
|
2009-01-13 21:27:19 -06:00
|
|
|
|
|
|
|
class Flag(Bool):
|
|
|
|
"""
|
|
|
|
A boolean parameter that always gets filled in with a default value.
|
|
|
|
|
2009-01-14 10:56:10 -06:00
|
|
|
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:
|
|
|
|
|
2009-01-14 11:17:39 -06:00
|
|
|
>>> 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)
|
2009-01-14 10:56:10 -06:00
|
|
|
|
|
|
|
Also note that creating a `Flag` instance with ``autofill=False`` will have
|
|
|
|
no effect. For example:
|
|
|
|
|
2009-01-14 11:17:39 -06:00
|
|
|
>>> flag = Flag('my_flag', autofill=False)
|
|
|
|
>>> flag.autofill
|
|
|
|
True
|
2009-01-13 21:27:19 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
|
|
|
kw['autofill'] = True
|
2009-01-14 10:56:10 -06:00
|
|
|
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))
|
|
|
|
)
|
2009-01-13 21:27:19 -06:00
|
|
|
super(Flag, self).__init__(name, *rules, **kw)
|
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2009-01-14 21:36:17 -06:00
|
|
|
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
|
2009-11-04 08:41:48 -06:00
|
|
|
if type(value) in (tuple, list):
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.scalar_error))
|
2009-01-14 21:36:17 -06:00
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.type_error),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Int(Number):
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2009-01-13 19:29:45 -06:00
|
|
|
A parameter for integer values (stored in the ``int`` type).
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2009-01-13 01:27:06 -06:00
|
|
|
type = int
|
2009-01-13 03:17:16 -06:00
|
|
|
type_error = _('must be an integer')
|
2009-01-13 01:27:06 -06:00
|
|
|
|
2009-03-03 15:12:29 -06:00
|
|
|
kwargs = Param.kwargs + (
|
2011-07-19 21:10:22 -05:00
|
|
|
('minvalue', int, int(MININT)),
|
2011-01-17 15:23:53 -06:00
|
|
|
('maxvalue', int, int(MAXINT)),
|
2009-03-03 15:12:29 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
2011-04-07 09:53:52 -05:00
|
|
|
#pylint: disable=E1003
|
2009-03-03 15:12:29 -06:00
|
|
|
super(Number, self).__init__(name, *rules, **kw)
|
|
|
|
|
|
|
|
if (self.minvalue > self.maxvalue) and (self.minvalue is not None and self.maxvalue is not None):
|
|
|
|
raise ValueError(
|
|
|
|
'%s: minvalue > maxvalue (minvalue=%r, maxvalue=%r)' % (
|
|
|
|
self.nice, self.minvalue, self.maxvalue)
|
|
|
|
)
|
|
|
|
|
2009-11-20 12:41:44 -06:00
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
"""
|
|
|
|
Convert a single scalar value.
|
|
|
|
"""
|
|
|
|
if type(value) in (int, long):
|
|
|
|
return value
|
2011-02-10 12:29:52 -06:00
|
|
|
if type(value) is unicode:
|
2009-11-20 12:41:44 -06:00
|
|
|
# permit floating point strings
|
|
|
|
if value.find(u'.') >= 0:
|
|
|
|
try:
|
|
|
|
return int(float(value))
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
# 2nd arg is radix base, 2nd arg only accepted for strings.
|
|
|
|
# Zero means determine radix base from prefix (e.g. 0x for hex)
|
|
|
|
return int(value, 0)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
if type(value) is float:
|
|
|
|
try:
|
|
|
|
return int(value)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.type_error),
|
|
|
|
)
|
|
|
|
|
2009-03-03 15:12:29 -06:00
|
|
|
def _rule_minvalue(self, _, value):
|
|
|
|
"""
|
|
|
|
Check min constraint.
|
|
|
|
"""
|
2011-07-19 21:10:22 -05:00
|
|
|
assert type(value) in (int, long)
|
|
|
|
if value < self.minvalue or value < MININT:
|
2011-07-26 00:58:41 -05:00
|
|
|
return _('must be at least %(minvalue)d') % dict(
|
2009-03-03 15:12:29 -06:00
|
|
|
minvalue=self.minvalue,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _rule_maxvalue(self, _, value):
|
|
|
|
"""
|
|
|
|
Check max constraint.
|
|
|
|
"""
|
2011-07-19 21:10:22 -05:00
|
|
|
assert type(value) in (int, long)
|
|
|
|
if value > self.maxvalue or value > MAXINT:
|
2009-03-03 15:12:29 -06:00
|
|
|
return _('can be at most %(maxvalue)d') % dict(
|
|
|
|
maxvalue=self.maxvalue,
|
|
|
|
)
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2011-07-14 02:14:07 -05:00
|
|
|
def _validate_scalar(self, value, index=None):
|
2011-07-19 21:10:22 -05:00
|
|
|
"""
|
|
|
|
This duplicates _validate_scalar in the Param class with
|
|
|
|
the exception that it allows both int and long types. The
|
|
|
|
min/max rules handle size enforcement.
|
|
|
|
"""
|
|
|
|
if type(value) not in (int, long):
|
|
|
|
raise ValidationError(name=self.name,
|
|
|
|
error='need a %r; got %r (a %r)' % (
|
|
|
|
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:
|
|
|
|
name = self.cli_name
|
|
|
|
if not name:
|
|
|
|
name = self.name
|
2011-07-14 02:14:07 -05:00
|
|
|
raise ValidationError(
|
2011-07-19 21:10:22 -05:00
|
|
|
name=name,
|
|
|
|
value=value,
|
|
|
|
index=index,
|
|
|
|
error=error,
|
|
|
|
rule=rule,
|
|
|
|
)
|
2011-07-14 02:14:07 -05:00
|
|
|
|
|
|
|
|
2009-01-14 21:36:17 -06:00
|
|
|
class Float(Number):
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2009-01-13 19:29:45 -06:00
|
|
|
A parameter for floating-point values (stored in the ``float`` type).
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2009-01-13 01:27:06 -06:00
|
|
|
type = float
|
2009-01-13 21:27:19 -06:00
|
|
|
type_error = _('must be a decimal number')
|
2009-01-13 01:27:06 -06:00
|
|
|
|
2009-03-03 15:12:29 -06:00
|
|
|
kwargs = Param.kwargs + (
|
|
|
|
('minvalue', float, None),
|
|
|
|
('maxvalue', float, None),
|
|
|
|
)
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
2011-04-07 09:53:52 -05:00
|
|
|
#pylint: disable=E1003
|
2009-03-03 15:12:29 -06:00
|
|
|
super(Number, self).__init__(name, *rules, **kw)
|
|
|
|
|
|
|
|
if (self.minvalue > self.maxvalue) and (self.minvalue is not None and self.maxvalue is not None):
|
|
|
|
raise ValueError(
|
|
|
|
'%s: minvalue > maxvalue (minvalue=%r, maxvalue=%r)' % (
|
|
|
|
self.nice, self.minvalue, self.maxvalue)
|
|
|
|
)
|
|
|
|
|
|
|
|
def _rule_minvalue(self, _, value):
|
|
|
|
"""
|
|
|
|
Check min constraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is float
|
|
|
|
if value < self.minvalue:
|
|
|
|
return _('must be at least %(minvalue)f') % dict(
|
|
|
|
minvalue=self.minvalue,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _rule_maxvalue(self, _, value):
|
|
|
|
"""
|
|
|
|
Check max constraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is float
|
|
|
|
if value > self.maxvalue:
|
|
|
|
return _('can be at most %(maxvalue)f') % dict(
|
|
|
|
maxvalue=self.maxvalue,
|
|
|
|
)
|
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2009-01-14 11:58:05 -06:00
|
|
|
class Data(Param):
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2009-01-14 11:58:05 -06:00
|
|
|
Base class for the `Bytes` and `Str` parameters.
|
2009-01-13 19:29:45 -06:00
|
|
|
|
2009-01-14 11:58:05 -06:00
|
|
|
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).
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2008-12-12 04:13:58 -06:00
|
|
|
kwargs = Param.kwargs + (
|
|
|
|
('minlength', int, None),
|
|
|
|
('maxlength', int, None),
|
|
|
|
('length', int, None),
|
2009-02-06 13:36:49 -06:00
|
|
|
('pattern', (basestring,), None),
|
2010-07-27 15:35:23 -05:00
|
|
|
('pattern_errmsg', (basestring,), None),
|
2008-12-12 04:13:58 -06:00
|
|
|
)
|
2011-06-24 13:32:57 -05:00
|
|
|
|
2011-04-21 03:13:06 -05:00
|
|
|
re = None
|
|
|
|
re_errmsg = None
|
2008-12-11 23:39:50 -06:00
|
|
|
|
2009-01-13 21:27:19 -06:00
|
|
|
def __init__(self, name, *rules, **kw):
|
2009-01-14 11:58:05 -06:00
|
|
|
super(Data, self).__init__(name, *rules, **kw)
|
2008-12-12 04:13:58 -06:00
|
|
|
|
|
|
|
if not (
|
|
|
|
self.length is None or
|
|
|
|
(self.minlength is None and self.maxlength is None)
|
|
|
|
):
|
|
|
|
raise ValueError(
|
|
|
|
'%s: cannot mix length with minlength or maxlength' % self.nice
|
|
|
|
)
|
|
|
|
|
|
|
|
if self.minlength is not None and self.minlength < 1:
|
|
|
|
raise ValueError(
|
|
|
|
'%s: minlength must be >= 1; got %r' % (self.nice, self.minlength)
|
|
|
|
)
|
|
|
|
|
|
|
|
if self.maxlength is not None and self.maxlength < 1:
|
|
|
|
raise ValueError(
|
|
|
|
'%s: maxlength must be >= 1; got %r' % (self.nice, self.maxlength)
|
|
|
|
)
|
|
|
|
|
|
|
|
if None not in (self.minlength, self.maxlength):
|
|
|
|
if self.minlength > self.maxlength:
|
|
|
|
raise ValueError(
|
|
|
|
'%s: minlength > maxlength (minlength=%r, maxlength=%r)' % (
|
|
|
|
self.nice, self.minlength, self.maxlength)
|
|
|
|
)
|
|
|
|
elif self.minlength == self.maxlength:
|
|
|
|
raise ValueError(
|
|
|
|
'%s: minlength == maxlength; use length=%d instead' % (
|
|
|
|
self.nice, self.minlength)
|
|
|
|
)
|
2008-12-10 22:14:05 -06:00
|
|
|
|
2009-02-06 13:36:49 -06:00
|
|
|
def _rule_pattern(self, _, value):
|
|
|
|
"""
|
|
|
|
Check pattern (regex) contraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is self.type
|
|
|
|
if self.re.match(value) is None:
|
2010-07-27 15:35:23 -05:00
|
|
|
if self.re_errmsg:
|
|
|
|
return self.re_errmsg % dict(pattern=self.pattern,)
|
|
|
|
else:
|
|
|
|
return _('must match pattern "%(pattern)s"') % dict(
|
|
|
|
pattern=self.pattern,
|
|
|
|
)
|
2009-02-06 13:36:49 -06:00
|
|
|
|
2009-01-14 11:58:05 -06:00
|
|
|
|
|
|
|
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
|
2009-01-21 18:19:39 -06:00
|
|
|
|
|
|
|
Also see the `Str` parameter.
|
2009-01-14 11:58:05 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
type = str
|
|
|
|
type_error = _('must be binary data')
|
|
|
|
|
2009-02-06 13:36:49 -06:00
|
|
|
def __init__(self, name, *rules, **kw):
|
|
|
|
if kw.get('pattern', None) is None:
|
|
|
|
self.re = None
|
|
|
|
else:
|
|
|
|
self.re = re.compile(kw['pattern'])
|
2010-07-27 15:35:23 -05:00
|
|
|
self.re_errmsg = kw.get('pattern_errmsg', None)
|
2009-02-06 13:36:49 -06:00
|
|
|
super(Bytes, self).__init__(name, *rules, **kw)
|
2009-01-14 11:58:05 -06:00
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_minlength(self, _, value):
|
2008-12-12 05:48:25 -06:00
|
|
|
"""
|
|
|
|
Check minlength constraint.
|
|
|
|
"""
|
2009-01-05 03:20:09 -06:00
|
|
|
assert type(value) is str
|
2008-12-12 05:48:25 -06:00
|
|
|
if len(value) < self.minlength:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('must be at least %(minlength)d bytes') % dict(
|
2008-12-12 05:48:25 -06:00
|
|
|
minlength=self.minlength,
|
|
|
|
)
|
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_maxlength(self, _, value):
|
2008-12-12 05:48:25 -06:00
|
|
|
"""
|
|
|
|
Check maxlength constraint.
|
|
|
|
"""
|
2009-01-05 03:20:09 -06:00
|
|
|
assert type(value) is str
|
2008-12-12 05:48:25 -06:00
|
|
|
if len(value) > self.maxlength:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('can be at most %(maxlength)d bytes') % dict(
|
2008-12-12 05:48:25 -06:00
|
|
|
maxlength=self.maxlength,
|
|
|
|
)
|
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_length(self, _, value):
|
2008-12-12 05:48:25 -06:00
|
|
|
"""
|
|
|
|
Check length constraint.
|
|
|
|
"""
|
2009-01-05 03:20:09 -06:00
|
|
|
assert type(value) is str
|
2008-12-12 05:48:25 -06:00
|
|
|
if len(value) != self.length:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('must be exactly %(length)d bytes') % dict(
|
2008-12-12 05:48:25 -06:00
|
|
|
length=self.length,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2009-01-14 11:58:05 -06:00
|
|
|
class Str(Data):
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
2009-01-14 10:56:10 -06:00
|
|
|
A parameter for Unicode text (stored in the ``unicode`` type).
|
2009-01-13 19:29:45 -06:00
|
|
|
|
|
|
|
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
|
2009-01-21 18:19:39 -06:00
|
|
|
|
|
|
|
Also see the `Bytes` parameter.
|
2008-12-10 22:14:05 -06:00
|
|
|
"""
|
|
|
|
|
2011-06-24 13:32:57 -05:00
|
|
|
kwargs = Data.kwargs + (
|
|
|
|
('noextrawhitespace', bool, True),
|
|
|
|
)
|
|
|
|
|
2008-12-11 23:39:50 -06:00
|
|
|
type = unicode
|
2009-01-13 19:29:45 -06:00
|
|
|
type_error = _('must be Unicode text')
|
2008-12-11 23:39:50 -06:00
|
|
|
|
2009-02-06 13:36:49 -06:00
|
|
|
def __init__(self, name, *rules, **kw):
|
|
|
|
if kw.get('pattern', None) is None:
|
|
|
|
self.re = None
|
|
|
|
else:
|
|
|
|
self.re = re.compile(kw['pattern'], re.UNICODE)
|
2010-07-27 15:35:23 -05:00
|
|
|
self.re_errmsg = kw.get('pattern_errmsg', None)
|
2009-02-06 13:36:49 -06:00
|
|
|
super(Str, self).__init__(name, *rules, **kw)
|
2008-12-12 04:13:58 -06:00
|
|
|
|
2008-12-10 22:14:05 -06:00
|
|
|
def _convert_scalar(self, value, index=None):
|
2009-01-13 19:29:45 -06:00
|
|
|
"""
|
|
|
|
Convert a single scalar value.
|
|
|
|
"""
|
|
|
|
if type(value) is self.type:
|
|
|
|
return value
|
|
|
|
if type(value) in (int, float):
|
2008-12-10 22:14:05 -06:00
|
|
|
return self.type(value)
|
2009-11-04 08:41:48 -06:00
|
|
|
if type(value) in (tuple, list):
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.scalar_error))
|
2009-01-13 19:29:45 -06:00
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=ugettext(self.type_error),
|
2008-12-10 22:14:05 -06:00
|
|
|
)
|
2009-01-05 03:45:07 -06:00
|
|
|
|
2011-06-24 13:32:57 -05:00
|
|
|
def _rule_noextrawhitespace(self, _, value):
|
|
|
|
"""
|
|
|
|
Do not allow leading/trailing spaces.
|
|
|
|
"""
|
|
|
|
assert type(value) is unicode
|
|
|
|
if self.noextrawhitespace is False: #pylint: disable=E1101
|
|
|
|
return
|
|
|
|
if len(value) != len(value.strip()):
|
|
|
|
return _('Leading and trailing spaces are not allowed')
|
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_minlength(self, _, value):
|
2009-01-05 03:45:07 -06:00
|
|
|
"""
|
|
|
|
Check minlength constraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is unicode
|
|
|
|
if len(value) < self.minlength:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('must be at least %(minlength)d characters') % dict(
|
2009-01-05 03:45:07 -06:00
|
|
|
minlength=self.minlength,
|
|
|
|
)
|
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_maxlength(self, _, value):
|
2009-01-05 03:45:07 -06:00
|
|
|
"""
|
|
|
|
Check maxlength constraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is unicode
|
|
|
|
if len(value) > self.maxlength:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('can be at most %(maxlength)d characters') % dict(
|
2009-01-05 03:45:07 -06:00
|
|
|
maxlength=self.maxlength,
|
|
|
|
)
|
|
|
|
|
2009-01-13 02:07:33 -06:00
|
|
|
def _rule_length(self, _, value):
|
2009-01-05 03:45:07 -06:00
|
|
|
"""
|
|
|
|
Check length constraint.
|
|
|
|
"""
|
|
|
|
assert type(value) is unicode
|
|
|
|
if len(value) != self.length:
|
2009-01-13 02:07:33 -06:00
|
|
|
return _('must be exactly %(length)d characters') % dict(
|
2009-01-05 03:45:07 -06:00
|
|
|
length=self.length,
|
|
|
|
)
|
2009-01-13 01:27:06 -06:00
|
|
|
|
|
|
|
|
2010-12-06 14:09:03 -06:00
|
|
|
class IA5Str(Str):
|
|
|
|
"""
|
|
|
|
An IA5String per RFC 4517
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
|
|
|
super(IA5Str, self).__init__(name, *rules, **kw)
|
|
|
|
|
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
if isinstance(value, basestring):
|
|
|
|
for i in xrange(len(value)):
|
|
|
|
if ord(value[i]) > 127:
|
|
|
|
raise ConversionError(name=self.name, index=index,
|
|
|
|
error=_('The character \'%(char)r\' is not allowed.') %
|
|
|
|
dict(char=value[i],)
|
|
|
|
)
|
|
|
|
return super(IA5Str, self)._convert_scalar(value, index)
|
|
|
|
|
|
|
|
|
2009-01-14 23:19:31 -06:00
|
|
|
class Password(Str):
|
|
|
|
"""
|
|
|
|
A parameter for passwords (stored in the ``unicode`` type).
|
|
|
|
"""
|
|
|
|
|
2011-08-24 17:10:22 -05:00
|
|
|
kwargs = Str.kwargs + (
|
|
|
|
('confirm', bool, True),
|
|
|
|
)
|
|
|
|
|
2009-10-16 03:22:39 -05:00
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
if isinstance(value, (tuple, list)) and len(value) == 2:
|
|
|
|
(p1, p2) = value
|
|
|
|
if p1 != p2:
|
|
|
|
raise PasswordMismatch(name=self.name, index=index)
|
|
|
|
value = p1
|
|
|
|
return super(Password, self)._convert_scalar(value, index)
|
|
|
|
|
2009-01-14 23:19:31 -06:00
|
|
|
|
2009-01-18 16:55:56 -06:00
|
|
|
class Enum(Param):
|
|
|
|
"""
|
|
|
|
Base class for parameters with enumerable values.
|
|
|
|
"""
|
|
|
|
|
|
|
|
kwargs = Param.kwargs + (
|
|
|
|
('values', tuple, tuple()),
|
|
|
|
)
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
|
|
|
super(Enum, self).__init__(name, *rules, **kw)
|
|
|
|
for (i, v) in enumerate(self.values):
|
|
|
|
if type(v) is not self.type:
|
|
|
|
n = '%s values[%d]' % (self.nice, i)
|
|
|
|
raise TypeError(
|
|
|
|
TYPE_ERROR % (n, self.type, v, type(v))
|
|
|
|
)
|
|
|
|
|
|
|
|
def _rule_values(self, _, value, **kw):
|
|
|
|
if value not in self.values:
|
|
|
|
return _('must be one of %(values)r') % dict(
|
|
|
|
values=self.values,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class BytesEnum(Enum):
|
|
|
|
"""
|
|
|
|
Enumerable for binary data (stored in the ``str`` type).
|
|
|
|
"""
|
|
|
|
|
|
|
|
type = unicode
|
|
|
|
|
|
|
|
|
|
|
|
class StrEnum(Enum):
|
|
|
|
"""
|
|
|
|
Enumerable for Unicode text (stored in the ``unicode`` type).
|
2009-01-18 17:03:02 -06:00
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
>>> enum = StrEnum('my_enum', values=(u'One', u'Two', u'Three'))
|
2010-10-11 21:27:57 -05:00
|
|
|
>>> enum.validate(u'Two', 'cli') is None
|
2009-01-18 17:03:02 -06:00
|
|
|
True
|
2010-10-11 21:27:57 -05:00
|
|
|
>>> enum.validate(u'Four', 'cli')
|
2009-01-18 17:03:02 -06:00
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
|
|
|
ValidationError: invalid 'my_enum': must be one of (u'One', u'Two', u'Three')
|
2009-01-18 16:55:56 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
type = unicode
|
|
|
|
|
|
|
|
|
2009-03-18 14:44:53 -05:00
|
|
|
class List(Param):
|
|
|
|
"""
|
|
|
|
Base class for parameters as a list of values. The input is a delimited
|
|
|
|
string.
|
|
|
|
"""
|
|
|
|
type = tuple
|
|
|
|
|
|
|
|
kwargs = Param.kwargs + (
|
|
|
|
('separator', str, ','),
|
|
|
|
('skipspace', bool, True),
|
|
|
|
)
|
|
|
|
|
|
|
|
# The following 2 functions were taken from the Python
|
|
|
|
# documentation at http://docs.python.org/library/csv.html
|
|
|
|
def __utf_8_encoder(self, unicode_csv_data):
|
|
|
|
for line in unicode_csv_data:
|
|
|
|
yield line.encode('utf-8')
|
|
|
|
|
|
|
|
def __unicode_csv_reader(self, unicode_csv_data, dialect=csv.excel, **kwargs):
|
|
|
|
# csv.py doesn't do Unicode; encode temporarily as UTF-8:
|
|
|
|
csv_reader = csv.reader(self.__utf_8_encoder(unicode_csv_data),
|
2011-06-15 12:06:14 -05:00
|
|
|
dialect=dialect,
|
|
|
|
delimiter=self.separator, escapechar='\\',
|
2009-03-18 14:44:53 -05:00
|
|
|
skipinitialspace=self.skipspace,
|
|
|
|
**kwargs)
|
|
|
|
for row in csv_reader:
|
|
|
|
# decode UTF-8 back to Unicode, cell by cell:
|
|
|
|
yield [unicode(cell, 'utf-8') for cell in row]
|
|
|
|
|
|
|
|
def __init__(self, name, *rules, **kw):
|
2009-06-01 11:59:58 -05:00
|
|
|
kw['multivalue'] = True
|
2009-03-18 14:44:53 -05:00
|
|
|
super(List, self).__init__(name, *rules, **kw)
|
|
|
|
|
|
|
|
def normalize(self, value):
|
2011-06-10 13:02:13 -05:00
|
|
|
if value and not type(value) in (list, tuple):
|
2009-03-18 14:44:53 -05:00
|
|
|
reader = self.__unicode_csv_reader([value])
|
|
|
|
value = []
|
|
|
|
for row in reader:
|
|
|
|
value = value + row
|
|
|
|
value = tuple(value)
|
|
|
|
return super(List, self).normalize(value)
|
|
|
|
|
|
|
|
def _convert_scalar(self, value, index=None):
|
|
|
|
return value
|
|
|
|
|
|
|
|
def _validate_scalar(self, value, index=None):
|
2010-11-03 10:30:03 -05:00
|
|
|
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,
|
|
|
|
)
|
2009-03-18 14:44:53 -05:00
|
|
|
|
2009-09-30 09:24:25 -05:00
|
|
|
|
2009-11-05 09:15:47 -06:00
|
|
|
class File(Str):
|
|
|
|
"""
|
|
|
|
File parameter type.
|
|
|
|
|
|
|
|
Accepts file names and loads their content into the parameter value.
|
|
|
|
"""
|
2011-07-22 09:23:31 -05:00
|
|
|
kwargs = Data.kwargs + (
|
2009-11-05 09:15:47 -06:00
|
|
|
# valid for CLI, other backends (e.g. webUI) can ignore this
|
|
|
|
('stdin_if_missing', bool, False),
|
2011-07-22 09:23:31 -05:00
|
|
|
('noextrawhitespace', bool, False),
|
2009-11-05 09:15:47 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2009-11-18 10:33:55 -06:00
|
|
|
class AccessTime(Str):
|
2009-09-30 09:24:25 -05:00
|
|
|
"""
|
2009-11-18 10:33:55 -06:00
|
|
|
Access time parameter type.
|
2009-09-30 09:24:25 -05:00
|
|
|
|
|
|
|
Accepts values conforming to generalizedTime as defined in RFC 4517
|
|
|
|
section 3.3.13 without time zone information.
|
|
|
|
"""
|
|
|
|
def _check_HHMM(self, t):
|
|
|
|
if len(t) != 4:
|
|
|
|
raise ValueError('HHMM must be exactly 4 characters long')
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('HHMM non-numeric')
|
|
|
|
hh = int(t[0:2])
|
|
|
|
if hh < 0 or hh > 23:
|
|
|
|
raise ValueError('HH out of range')
|
|
|
|
mm = int(t[2:4])
|
|
|
|
if mm < 0 or mm > 59:
|
|
|
|
raise ValueError('MM out of range')
|
2009-10-13 12:28:00 -05:00
|
|
|
|
2009-09-30 09:24:25 -05:00
|
|
|
def _check_dotw(self, t):
|
|
|
|
if t.isnumeric():
|
|
|
|
value = int(t)
|
|
|
|
if value < 1 or value > 7:
|
|
|
|
raise ValueError('day of the week out of range')
|
|
|
|
elif t not in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'):
|
|
|
|
raise ValueError('invalid day of the week')
|
|
|
|
|
|
|
|
def _check_dotm(self, t, month_num=1, year=4):
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('day of the month non-numeric')
|
|
|
|
value = int(t)
|
|
|
|
if month_num in (1, 3, 5, 7, 8, 10, 12):
|
|
|
|
if value < 1 or value > 31:
|
|
|
|
raise ValueError('day of the month out of range')
|
|
|
|
elif month_num in (4, 6, 9, 11):
|
|
|
|
if value < 1 or value > 30:
|
|
|
|
raise ValueError('day of the month out of range')
|
|
|
|
elif month_num == 2:
|
|
|
|
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
|
|
|
|
if value < 1 or value > 29:
|
|
|
|
raise ValueError('day of the month out of range')
|
|
|
|
else:
|
|
|
|
if value < 1 or value > 28:
|
|
|
|
raise ValueError('day of the month out of range')
|
|
|
|
|
|
|
|
def _check_wotm(self, t):
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('week of the month non-numeric')
|
|
|
|
value = int(t)
|
2010-06-03 08:25:25 -05:00
|
|
|
if value < 1 or value > 6:
|
2009-09-30 09:24:25 -05:00
|
|
|
raise ValueError('week of the month out of range')
|
|
|
|
|
|
|
|
def _check_woty(self, t):
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('week of the year non-numeric')
|
|
|
|
value = int(t)
|
|
|
|
if value < 1 or value > 52:
|
|
|
|
raise ValueError('week of the year out of range')
|
|
|
|
|
2011-01-25 11:46:26 -06:00
|
|
|
def _check_doty(self, t):
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('day of the year non-numeric')
|
|
|
|
value = int(t)
|
|
|
|
if value < 1 or value > 365:
|
|
|
|
raise ValueError('day of the year out of range')
|
|
|
|
|
2009-09-30 09:24:25 -05:00
|
|
|
def _check_month_num(self, t):
|
|
|
|
if not t.isnumeric():
|
|
|
|
raise ValueError('month number non-numeric')
|
|
|
|
value = int(t)
|
|
|
|
if value < 1 or value > 12:
|
2009-10-13 12:28:00 -05:00
|
|
|
raise ValueError('month number out of range')
|
2009-09-30 09:24:25 -05:00
|
|
|
|
|
|
|
def _check_interval(self, t, check_func):
|
|
|
|
intervals = t.split(',')
|
|
|
|
for i in intervals:
|
|
|
|
if not i:
|
|
|
|
raise ValueError('invalid time range')
|
|
|
|
values = i.split('-')
|
|
|
|
if len(values) > 2:
|
|
|
|
raise ValueError('invalid time range')
|
|
|
|
for v in values:
|
|
|
|
check_func(v)
|
|
|
|
if len(values) == 2:
|
2010-04-30 11:02:28 -05:00
|
|
|
if int(values[0]) > int(values[1]):
|
2009-09-30 09:24:25 -05:00
|
|
|
raise ValueError('invalid time range')
|
|
|
|
|
|
|
|
def _check_W_spec(self, ts, index):
|
|
|
|
if ts[index] != 'day':
|
|
|
|
raise ValueError('invalid week specifier')
|
|
|
|
index += 1
|
|
|
|
self._check_interval(ts[index], self._check_dotw)
|
|
|
|
return index
|
|
|
|
|
|
|
|
def _check_M_spec(self, ts, index):
|
|
|
|
if ts[index] == 'week':
|
|
|
|
self._check_interval(ts[index + 1], self._check_wotm)
|
|
|
|
index = self._check_W_spec(ts, index + 2)
|
|
|
|
elif ts[index] == 'day':
|
|
|
|
index += 1
|
|
|
|
self._check_interval(ts[index], self._check_dotm)
|
|
|
|
else:
|
|
|
|
raise ValueError('invalid month specifier')
|
|
|
|
return index
|
|
|
|
|
|
|
|
def _check_Y_spec(self, ts, index):
|
|
|
|
if ts[index] == 'month':
|
|
|
|
index += 1
|
|
|
|
self._check_interval(ts[index], self._check_month_num)
|
|
|
|
month_num = int(ts[index])
|
2010-04-30 11:02:28 -05:00
|
|
|
index = self._check_M_spec(ts, index + 1)
|
2009-09-30 09:24:25 -05:00
|
|
|
elif ts[index] == 'week':
|
|
|
|
self._check_interval(ts[index + 1], self._check_woty)
|
|
|
|
index = self._check_W_spec(ts, index + 2)
|
|
|
|
elif ts[index] == 'day':
|
|
|
|
index += 1
|
|
|
|
self._check_interval(ts[index], self._check_doty)
|
|
|
|
else:
|
|
|
|
raise ValueError('invalid year specifier')
|
|
|
|
return index
|
|
|
|
|
|
|
|
def _check_generalized(self, t):
|
2010-04-30 11:02:28 -05:00
|
|
|
assert type(t) is unicode
|
2009-09-30 09:24:25 -05:00
|
|
|
if len(t) not in (10, 12, 14):
|
|
|
|
raise ValueError('incomplete generalized time')
|
|
|
|
if not t.isnumeric():
|
2009-11-18 10:33:55 -06:00
|
|
|
raise ValueError('time non-numeric')
|
2009-09-30 09:24:25 -05:00
|
|
|
# don't check year value, with time travel and all :)
|
|
|
|
self._check_month_num(t[4:6])
|
|
|
|
year_num = int(t[0:4])
|
|
|
|
month_num = int(t[4:6])
|
|
|
|
self._check_dotm(t[6:8], month_num, year_num)
|
|
|
|
if len(t) >= 12:
|
|
|
|
self._check_HHMM(t[8:12])
|
|
|
|
else:
|
|
|
|
self._check_HHMM('%s00' % t[8:10])
|
|
|
|
if len(t) == 14:
|
|
|
|
s = int(t[12:14])
|
|
|
|
if s < 0 or s > 60:
|
|
|
|
raise ValueError('seconds out of range')
|
|
|
|
|
|
|
|
def _check(self, time):
|
|
|
|
ts = time.split()
|
|
|
|
if ts[0] == 'absolute':
|
2010-04-30 11:02:28 -05:00
|
|
|
if len(ts) != 4:
|
|
|
|
raise ValueError('invalid format, must be \'absolute generalizedTime ~ generalizedTime\'')
|
2009-09-30 09:24:25 -05:00
|
|
|
self._check_generalized(ts[1])
|
|
|
|
if ts[2] != '~':
|
|
|
|
raise ValueError('invalid time range separator')
|
|
|
|
self._check_generalized(ts[3])
|
|
|
|
if int(ts[1]) >= int(ts[3]):
|
2009-11-18 10:33:55 -06:00
|
|
|
raise ValueError('invalid time range')
|
2009-09-30 09:24:25 -05:00
|
|
|
elif ts[0] == 'periodic':
|
2010-04-30 11:02:28 -05:00
|
|
|
index = None
|
2009-09-30 09:24:25 -05:00
|
|
|
if ts[1] == 'yearly':
|
|
|
|
index = self._check_Y_spec(ts, 2)
|
|
|
|
elif ts[1] == 'monthly':
|
|
|
|
index = self._check_M_spec(ts, 2)
|
2010-05-04 13:12:55 -05:00
|
|
|
elif ts[1] == 'weekly':
|
|
|
|
index = self._check_W_spec(ts, 2)
|
2009-09-30 09:24:25 -05:00
|
|
|
elif ts[1] == 'daily':
|
|
|
|
index = 1
|
2010-04-30 11:02:28 -05:00
|
|
|
if index is None:
|
|
|
|
raise ValueError('period must be yearly, monthy or daily, got \'%s\'' % ts[1])
|
2009-09-30 09:24:25 -05:00
|
|
|
self._check_interval(ts[index + 1], self._check_HHMM)
|
|
|
|
else:
|
|
|
|
raise ValueError('time neither absolute or periodic')
|
|
|
|
|
|
|
|
def _rule_required(self, _, value):
|
|
|
|
try:
|
|
|
|
self._check(value)
|
|
|
|
except ValueError, e:
|
2010-04-30 11:02:28 -05:00
|
|
|
raise ValidationError(name=self.cli_name, error=e.args[0])
|
2009-09-30 09:24:25 -05:00
|
|
|
except IndexError:
|
|
|
|
raise ValidationError(
|
2010-04-30 11:02:28 -05:00
|
|
|
name=self.cli_name, error='incomplete time value'
|
2009-09-30 09:24:25 -05:00
|
|
|
)
|
2009-10-13 12:28:00 -05:00
|
|
|
return None
|
2009-09-30 09:24:25 -05:00
|
|
|
|
|
|
|
|
2009-01-13 01:27:06 -06:00
|
|
|
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
|
2009-01-13 03:17:16 -06:00
|
|
|
required, and whether it is multivalue. (For the definition of the
|
|
|
|
shorthand syntax, see the `parse_param_spec()` function.)
|
2009-01-13 01:27:06 -06:00
|
|
|
|
|
|
|
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)
|