Change parameters to use only default_from for dynamic default values.

Replace all occurences of create_default with equivalent default_from
and remove create_default from the framework. This is needed for
proper parameter validation, as there is no way to tell which
parameters to validate prior to calling create_default, because
create_default does not provide information about which parameters are
used for generating the default value.
This commit is contained in:
Jan Cholasta 2012-03-15 04:32:37 -04:00 committed by Martin Kosek
parent 5a55e11a25
commit a2299070c8
7 changed files with 30 additions and 115 deletions

View File

@ -198,6 +198,8 @@ class DefaultFrom(ReadOnly):
self.callback = callback
if len(keys) == 0:
fc = callback.func_code
if fc.co_flags & 0x0c:
raise ValueError("callback: variable-length argument list not allowed")
self.keys = fc.co_varnames[:fc.co_argcount]
else:
self.keys = keys
@ -308,13 +310,9 @@ class Param(ReadOnly):
encoder
- default_from: a custom function for generating default values of
parameter instance
- create_default: a custom function for generating default values of
parameter instance. Unlike default_from attribute, this function
is not wrapped. `Param.get_default()` documentation provides further
details
- autofill: by default, only `required` parameters get a default value
from default_from or create_default functions. When autofill is
enabled, optional attributes get the default value filled too
from the default_from function. When autofill is enabled, optional
attributes get the default value filled too
- query: this attribute is controlled by framework. When the `query`
is enabled, framework assumes that the value is only queried and not
inserted in the LDAP. Validation is then relaxed - custom
@ -379,7 +377,6 @@ class Param(ReadOnly):
('normalizer', callable, None),
('encoder', callable, None),
('default_from', DefaultFrom, None),
('create_default', callable, None),
('autofill', bool, False),
('query', bool, False),
('attribute', bool, False),
@ -481,20 +478,6 @@ 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 only 'include' or 'exclude' was provided:
if None not in (self.include, self.exclude):
raise ValueError(
@ -990,59 +973,9 @@ class Param(ReadOnly):
>>> 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 self.default_from is not None:
default = self.default_from(**kw)
if default is not None:
try:
return self.convert(self.normalize(default))

View File

@ -234,7 +234,7 @@ def _rname_validator(ugettext, zonemgr):
return unicode(e)
return None
def _create_zone_serial(**kwargs):
def _create_zone_serial():
""" Generate serial number for zones. The format follows RFC 1912 """
return int('%s01' % time.strftime('%Y%m%d'))
@ -1554,7 +1554,7 @@ class dnszone(LDAPObject):
label=_('SOA serial'),
doc=_('SOA record serial number'),
minvalue=1,
create_default=_create_zone_serial,
default_from=_create_zone_serial,
autofill=True,
),
Int('idnssoarefresh',

View File

@ -69,7 +69,7 @@ class passwd(Command):
label=_('User name'),
primary_key=True,
autofill=True,
create_default=lambda **kw: util.get_current_principal(),
default_from=lambda: util.get_current_principal(),
normalizer=lambda value: normalize_principal(value),
),
Password('password',

View File

@ -52,7 +52,7 @@ class join(Command):
validate_host,
cli_name='hostname',
doc=_("The hostname to register as"),
create_default=lambda **kw: unicode(util.get_fqdn()),
default_from=lambda: unicode(util.get_fqdn()),
autofill=True,
#normalizer=lamda value: value.lower(),
),
@ -60,7 +60,7 @@ class join(Command):
takes_options= (
Str('realm',
doc=_("The IPA realm"),
create_default=lambda **kw: get_realm(),
default_from=lambda: get_realm(),
autofill=True,
),
Str('nshardwareplatform?',

View File

@ -56,9 +56,9 @@ class IPATypeChecker(TypeChecker):
'ipalib.plugins.misc.env': ['env'],
'ipalib.parameters.Param': ['cli_name', 'cli_short_name', 'label',
'doc', 'required', 'multivalue', 'primary_key', 'normalizer',
'default', 'default_from', 'create_default', 'autofill', 'query',
'attribute', 'include', 'exclude', 'flags', 'hint', 'alwaysask',
'sortorder', 'csv', 'csv_separator', 'csv_skipspace'],
'default', 'default_from', 'autofill', 'query', 'attribute',
'include', 'exclude', 'flags', 'hint', 'alwaysask', 'sortorder',
'csv', 'csv_separator', 'csv_skipspace'],
'ipalib.parameters.Bool': ['truths', 'falsehoods'],
'ipalib.parameters.Int': ['minvalue', 'maxvalue'],
'ipalib.parameters.Decimal': ['minvalue', 'maxvalue', 'precision'],

View File

@ -45,7 +45,6 @@ PARAM_IGNORED_KW_ATTRIBUTES = ('label',
'normalizer',
'encoder',
'default_from',
'create_default',
'hint',
'flags',
'sortorder',)

View File

@ -64,6 +64,16 @@ class test_DefaultFrom(ClassChecker):
e = raises(TypeError, self.cls, callback, 'givenname', 17)
assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
# Test that ValueError is raised when inferring keys from a callback
# which has *args:
e = raises(ValueError, self.cls, lambda foo, *args: None)
assert str(e) == "callback: variable-length argument list not allowed"
# Test that ValueError is raised when inferring keys from a callback
# which has **kwargs:
e = raises(ValueError, self.cls, lambda foo, **kwargs: None)
assert str(e) == "callback: variable-length argument list not allowed"
def test_repr(self):
"""
Test the `ipalib.parameters.DefaultFrom.__repr__` method.
@ -185,8 +195,6 @@ class test_Param(ClassChecker):
assert o.normalizer is None
assert o.default is None
assert o.default_from is None
assert o.create_default is None
assert o._get_default is None
assert o.autofill is False
assert o.query is False
assert o.attribute is False
@ -250,16 +258,6 @@ class test_Param(ClassChecker):
assert str(e) == \
"Param('my_param'): takes no such kwargs: 'ape', 'great'"
# Test that ValueError is raised if you provide both default_from and
# create_default:
e = raises(ValueError, self.cls, 'my_param',
default_from=lambda first, last: first[0] + last,
create_default=lambda **kw: 'The Default'
)
assert str(e) == '%s: cannot have both %r and %r' % (
"Param('my_param')", 'default_from', 'create_default',
)
# Test that ValueError is raised if you provide both include and
# exclude:
e = raises(ValueError, self.cls, 'my_param',
@ -276,15 +274,11 @@ class test_Param(ClassChecker):
e = raises(ValueError, self.cls, 'my_param', csv=True)
assert str(e) == '%s: cannot have csv without multivalue' % "Param('my_param')"
# Test that _get_default gets set:
call1 = lambda first, last: first[0] + last
call2 = lambda **kw: 'The Default'
o = self.cls('my_param', default_from=call1)
assert o.default_from.callback is call1
assert o._get_default is o.default_from
o = self.cls('my_param', create_default=call2)
assert o.create_default is call2
assert o._get_default is call2
# Test that default_from gets set:
call = lambda first, last: first[0] + last
o = self.cls('my_param', default_from=call)
assert type(o.default_from) is parameters.DefaultFrom
assert o.default_from.callback is call
def test_repr(self):
"""
@ -579,7 +573,7 @@ class test_Param(ClassChecker):
def test_get_default(self):
"""
Test the `ipalib.parameters.Param._get_default` method.
Test the `ipalib.parameters.Param.get_default` method.
"""
class PassThrough(object):
value = None
@ -624,17 +618,6 @@ class test_Param(ClassChecker):
assert o._convert_scalar.value is default
assert o.normalizer.value is default
# Test with create_default:
o = Str('my_str',
normalizer=PassThrough(),
default=u'Static Default',
create_default=lambda **kw: u'The created default',
)
default = o.get_default(first=u'john', last='doe')
assert_equal(default, u'The created default')
assert o._convert_scalar.value is default
assert o.normalizer.value is default
def test_split_csv(self):
"""
Test the `ipalib.parameters.Param.split_csv` method with csv.