Removed depreciated code from frontend.py; frontend.py no longer imports ipa_types

This commit is contained in:
Jason Gerard DeRose 2009-01-14 11:39:29 -07:00
parent f3e0900ebc
commit 2b2e73e7df
2 changed files with 3 additions and 864 deletions

View File

@ -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
import parameters
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.
@ -810,7 +363,7 @@ class LocalOrRemote(Command):
"""
takes_options = (
Param('server?', type=ipa_types.Bool(), default=False,
parameters.Flag('server?',
doc='Forward to server instead of running locally',
),
)
@ -1064,7 +617,7 @@ class Property(Attribute):
'type',
)).union(Attribute.__public__)
type = ipa_types.Unicode()
type = parameters.Str
required = False
multivalue = False
default = None

View File

@ -70,420 +70,6 @@ def test_is_rule():
assert not is_rule(call(None))
class test_DefaultFrom(ClassChecker):
"""
Test the `ipalib.frontend.DefaultFrom` class.
"""
_cls = frontend.DefaultFrom
def test_class(self):
"""
Test the `ipalib.frontend.DefaultFrom` class.
"""
assert self.cls.__bases__ == (plugable.ReadOnly,)
def test_init(self):
"""
Test the `ipalib.frontend.DefaultFrom.__init__` method.
"""
def callback(*args):
return args
keys = ('givenname', 'sn')
o = self.cls(callback, *keys)
assert read_only(o, 'callback') is callback
assert read_only(o, 'keys') == keys
lam = lambda first, last: first[0] + last
o = self.cls(lam)
assert read_only(o, 'keys') == ('first', 'last')
def test_call(self):
"""
Test the `ipalib.frontend.DefaultFrom.__call__` method.
"""
def callback(givenname, sn):
return givenname[0] + sn[0]
keys = ('givenname', 'sn')
o = self.cls(callback, *keys)
kw = dict(
givenname='John',
sn='Public',
hello='world',
)
assert o(**kw) == 'JP'
assert o() is None
for key in ('givenname', 'sn'):
kw_copy = dict(kw)
del kw_copy[key]
assert o(**kw_copy) is None
# Test using implied keys:
o = self.cls(lambda first, last: first[0] + last)
assert o(first='john', last='doe') == 'jdoe'
assert o(first='', last='doe') is None
assert o(one='john', two='doe') is None
# Test that co_varnames slice is used:
def callback2(first, last):
letter = first[0]
return letter + last
o = self.cls(callback2)
assert o.keys == ('first', 'last')
assert o(first='john', last='doe') == 'jdoe'
def test_parse_param_spec():
"""
Test the `ipalib.frontend.parse_param_spec` function.
"""
f = frontend.parse_param_spec
assert f('name') == ('name', dict(required=True, multivalue=False))
assert f('name?') == ('name', dict(required=False, multivalue=False))
assert f('name*') == ('name', dict(required=False, multivalue=True))
assert f('name+') == ('name', dict(required=True, multivalue=True))
class test_Param(ClassChecker):
"""
Test the `ipalib.frontend.Param` class.
"""
_cls = frontend.Param
def test_class(self):
"""
Test the `ipalib.frontend.Param` class.
"""
assert self.cls.__bases__ == (plugable.ReadOnly,)
def test_init(self):
"""
Test the `ipalib.frontend.Param.__init__` method.
"""
name = 'sn'
o = self.cls(name)
assert o.__islocked__() is True
# Test default values
assert read_only(o, 'name') is name
assert read_only(o, 'cli_name') is name
assert isinstance(read_only(o, 'type'), ipa_types.Unicode)
assert read_only(o, 'doc') == ''
assert read_only(o, 'required') is True
assert read_only(o, 'multivalue') is False
assert read_only(o, 'default') is None
assert read_only(o, 'default_from') is None
assert read_only(o, 'flags') == frozenset()
assert read_only(o, 'rules') == tuple()
assert len(read_only(o, 'all_rules')) == 1
assert read_only(o, 'primary_key') is False
# Test all kw args:
t = ipa_types.Int()
assert self.cls(name, cli_name='last').cli_name == 'last'
assert self.cls(name, type=t).type is t
assert self.cls(name, doc='the doc').doc == 'the doc'
assert self.cls(name, required=False).required is False
assert self.cls(name, multivalue=True).multivalue is True
assert self.cls(name, default=u'Hello').default == u'Hello'
df = frontend.DefaultFrom(lambda f, l: f + l,
'first', 'last',
)
lam = lambda first, last: first + last
for cb in (df, lam):
o = self.cls(name, default_from=cb)
assert type(o.default_from) is frontend.DefaultFrom
assert o.default_from.keys == ('first', 'last')
assert o.default_from.callback('butt', 'erfly') == 'butterfly'
assert self.cls(name, flags=('one', 'two', 'three')).flags == \
frozenset(['one', 'two', 'three'])
rules = (lambda whatever: 'Not okay!',)
o = self.cls(name, rules=rules)
assert o.rules is rules
assert o.all_rules[1:] == rules
assert self.cls(name, primary_key=True).primary_key is True
# Test default type_:
o = self.cls(name)
assert isinstance(o.type, ipa_types.Unicode)
# Test param spec parsing:
o = self.cls('name?')
assert o.name == 'name'
assert o.required is False
assert o.multivalue is False
o = self.cls('name*')
assert o.name == 'name'
assert o.required is False
assert o.multivalue is True
o = self.cls('name+')
assert o.name == 'name'
assert o.required is True
assert o.multivalue is True
e = raises(TypeError, self.cls, name, whatever=True, another=False)
assert str(e) == \
'Param.__init__() takes no such kwargs: another, whatever'
def test_ispassword(self):
"""
Test the `ipalib.frontend.Param.ispassword` method.
"""
name = 'userpassword'
okay = 'password'
nope = ['', 'pass', 'word', 'passwd']
for flag in nope:
o = self.cls(name, flags=[flag])
assert o.ispassword() is False
o = self.cls(name, flags=[flag, okay])
assert o.ispassword() is True
assert self.cls(name).ispassword() is False
assert self.cls(name, flags=[okay]).ispassword() is True
assert self.cls(name, flags=[okay]+nope).ispassword() is True
def test_clone(self):
"""
Test the `ipalib.frontend.Param.__clone__` method.
"""
def compare(o, kw):
for (k, v) in kw.iteritems():
assert getattr(o, k) == v, (k, v, getattr(o, k))
default = dict(
required=False,
multivalue=False,
default=None,
default_from=None,
rules=tuple(),
)
name = 'hair_color?'
type_ = ipa_types.Int()
o = self.cls(name, type=type_)
compare(o, default)
override = dict(multivalue=True, default=42)
d = dict(default)
d.update(override)
clone = o.__clone__(**override)
assert clone.name == 'hair_color'
assert clone.type is o.type
compare(clone, d)
def test_convert(self):
"""
Test the `ipalib.frontend.Param.convert` method.
"""
name = 'some_number'
type_ = ipa_types.Int()
okay = (7, 7L, 7.0, ' 7 ')
fail = ('7.0', '7L', 'whatever', object)
none = (None, '', u'', tuple(), [])
# Scenario 1: multivalue=False
o = self.cls(name, type=type_)
for n in none:
assert o.convert(n) is None
for value in okay:
new = o.convert(value)
assert new == 7
assert type(new) is int
for value in fail:
e = raises(errors.ConversionError, o.convert, value)
assert e.name is name
assert e.value is value
assert e.error is type_.conversion_error
assert e.index is None
# Scenario 2: multivalue=True
o = self.cls(name, type=type_, multivalue=True)
for n in none:
assert o.convert(n) is None
for value in okay:
assert o.convert((value,)) == (7,)
assert o.convert([value]) == (7,)
assert o.convert(okay) == tuple(int(v) for v in okay)
cnt = 5
for value in fail:
for i in xrange(cnt):
others = list(7 for x in xrange(cnt))
others[i] = value
for v in [tuple(others), list(others)]:
e = raises(errors.ConversionError, o.convert, v)
assert e.name is name
assert e.value is value
assert e.error is type_.conversion_error
assert e.index == i
def test_normalize(self):
"""
Test the `ipalib.frontend.Param.normalize` method.
"""
name = 'sn'
callback = lambda value: value.lower()
values = (None, u'Hello', (u'Hello',), 'hello', ['hello'])
none = (None, '', u'', tuple(), [])
# Scenario 1: multivalue=False, normalize=None
o = self.cls(name)
for v in values:
# When normalize=None, value is returned, no type checking:
assert o.normalize(v) is v
# Scenario 2: multivalue=False, normalize=callback
o = self.cls(name, normalize=callback)
for v in (u'Hello', u'hello', 'Hello'): # Okay
assert o.normalize(v) == 'hello'
for v in [None, 42, (u'Hello',)]: # Not basestring
assert o.normalize(v) is v
for n in none:
assert o.normalize(n) is None
# Scenario 3: multivalue=True, normalize=None
o = self.cls(name, multivalue=True)
for v in values:
# When normalize=None, value is returned, no type checking:
assert o.normalize(v) is v
# Scenario 4: multivalue=True, normalize=callback
o = self.cls(name, multivalue=True, normalize=callback)
assert o.normalize([]) is None
assert o.normalize(tuple()) is None
for value in [(u'Hello',), (u'hello',), 'Hello', ['Hello']]: # Okay
assert o.normalize(value) == (u'hello',)
fail = 42 # Not basestring
for v in [[fail], (u'hello', fail)]: # Non basestring member
assert o.normalize(v) == tuple(v)
for n in none:
assert o.normalize(n) is None
def test_validate(self):
"""
Test the `ipalib.frontend.Param.validate` method.
"""
name = 'sn'
type_ = ipa_types.Unicode()
def case_rule(value):
if not value.islower():
return 'Must be lower case'
my_rules = (case_rule,)
okay = u'whatever'
fail_case = u'Whatever'
fail_type = 'whatever'
# Scenario 1: multivalue=False
o = self.cls(name, type=type_, rules=my_rules)
assert o.rules == my_rules
assert o.all_rules == (type_.validate, case_rule)
o.validate(okay)
e = raises(errors.RuleError, o.validate, fail_case)
assert e.name is name
assert e.value is fail_case
assert e.error == 'Must be lower case'
assert e.rule is case_rule
assert e.index is None
check_TypeError(fail_type, unicode, 'value', o.validate, fail_type)
## Scenario 2: multivalue=True
o = self.cls(name, type=type_, multivalue=True, rules=my_rules)
o.validate((okay,))
cnt = 5
for i in xrange(cnt):
others = list(okay for x in xrange(cnt))
others[i] = fail_case
value = tuple(others)
e = raises(errors.RuleError, o.validate, value)
assert e.name is name
assert e.value is fail_case
assert e.error == 'Must be lower case'
assert e.rule is case_rule
assert e.index == i
for not_tuple in (okay, [okay]):
check_TypeError(not_tuple, tuple, 'value', o.validate, not_tuple)
for has_str in [(fail_type,), (okay, fail_type)]:
check_TypeError(fail_type, unicode, 'value', o.validate, has_str)
def test_get_default(self):
"""
Test the `ipalib.frontend.Param.get_default` method.
"""
name = 'greeting'
default = u'Hello, world!'
default_from = frontend.DefaultFrom(
lambda first, last: u'Hello, %s %s!' % (first, last),
'first', 'last'
)
# Scenario 1: multivalue=False
o = self.cls(name,
default=default,
default_from=default_from,
)
assert o.default is default
assert o.default_from is default_from
assert o.get_default() == default
assert o.get_default(first='John', last='Doe') == 'Hello, John Doe!'
# Scenario 2: multivalue=True
default = (default,)
o = self.cls(name,
default=default,
default_from=default_from,
multivalue=True,
)
assert o.default is default
assert o.default_from is default_from
assert o.get_default() == default
assert o.get_default(first='John', last='Doe') == ('Hello, John Doe!',)
def test_get_value(self):
"""
Test the `ipalib.frontend.Param.get_values` method.
"""
name = 'status'
values = (u'Active', u'Inactive')
o = self.cls(name, type=ipa_types.Unicode())
assert o.get_values() == tuple()
o = self.cls(name, type=ipa_types.Enum(*values))
assert o.get_values() == values
def test_repr(self):
"""
Test the `ipalib.frontend.Param.__repr__` method.
"""
for name in ['name', 'name?', 'name*', 'name+']:
o = self.cls(name)
assert repr(o) == 'Param(%r)' % name
o = self.cls('name', required=False)
assert repr(o) == "Param('name', required=False)"
o = self.cls('name', multivalue=True)
assert repr(o) == "Param('name', multivalue=True)"
def test_create_param():
"""
Test the `ipalib.frontend.create_param` function.
"""
f = frontend.create_param
for name in ['arg', 'arg?', 'arg*', 'arg+']:
o = f(name)
assert type(o) is frontend.Param
assert type(o.type) is ipa_types.Unicode
assert o.name == 'arg'
assert f(o) is o
o = f('arg')
assert o.required is True
assert o.multivalue is False
o = f('arg?')
assert o.required is False
assert o.multivalue is False
o = f('arg*')
assert o.required is False
assert o.multivalue is True
o = f('arg+')
assert o.required is True
assert o.multivalue is True
class test_Command(ClassChecker):
"""
Test the `ipalib.frontend.Command` class.