Added Param 'include' and 'exclude' kwargs; added frontend.UsesParams base class with methods implementing the filtering to restrict params to only certain contexts

This commit is contained in:
Jason Gerard DeRose 2009-05-13 01:04:35 -06:00
parent 86472a94ee
commit 4f9224774f
4 changed files with 194 additions and 8 deletions

View File

@ -198,9 +198,11 @@ class Env(object):
__locked = False
def __init__(self):
def __init__(self, **initialize):
object.__setattr__(self, '_Env__d', {})
object.__setattr__(self, '_Env__done', set())
if initialize:
self._merge(**initialize)
def __lock__(self):
"""

View File

@ -25,7 +25,7 @@ import re
import inspect
import plugable
from plugable import lock, check_name
from parameters import create_param, Param, Str, Flag, Password
from parameters import create_param, parse_param_spec, Param, Str, Flag, Password
from util import make_repr
from errors import ZeroArgumentError, MaxArgumentError, OverlapError, RequiresRoot
@ -43,6 +43,111 @@ def is_rule(obj):
return callable(obj) and getattr(obj, RULE_FLAG, False) is True
class UsesParams(plugable.Plugin):
"""
Base class for plugins that use param namespaces.
"""
def _get_params_iterable(self, name):
"""
Return an iterable of params defined by the attribute named ``name``.
A sequence of params can be defined one of three ways: as a ``tuple``;
as a callable that returns an iterable; or as a param spec (a `Param` or
``str`` instance). This method returns a uniform iterable regardless of
how the param sequence was defined.
For example, when defined with a tuple:
>>> class ByTuple(UsesParams):
... takes_args = (Param('foo'), Param('bar'))
...
>>> by_tuple = ByTuple()
>>> list(by_tuple._get_params_iterable('takes_args'))
[Param('foo'), Param('bar')]
Or you can define your param sequence with a callable when you need to
reference attributes on your plugin instance (for validation rules,
etc.). For example:
>>> class ByCallable(UsesParams):
... def takes_args(self):
... yield Param('foo', self.validate_foo)
... yield Param('bar', self.validate_bar)
...
... def validate_foo(self, _, value, **kw):
... if value != 'Foo':
... return _("must be 'Foo'")
...
... def validate_bar(self, _, value, **kw):
... if value != 'Bar':
... return _("must be 'Bar'")
...
>>> by_callable = ByCallable()
>>> list(by_callable._get_params_iterable('takes_args'))
[Param('foo', validate_foo), Param('bar', validate_bar)]
Lastly, as a convenience for when a param sequence contains a single
param, your defining attribute may a param spec (either a `Param`
or an ``str`` instance). For example:
>>> class BySpec(UsesParams):
... takes_args = Param('foo')
... takes_options = 'bar?'
...
>>> by_spec = BySpec()
>>> list(by_spec._get_params_iterable('takes_args'))
[Param('foo')]
>>> list(by_spec._get_params_iterable('takes_options'))
['bar?']
For information on how an ``str`` param spec is interpreted, see the
`create_param()` and `parse_param_spec()` functions in the
`ipalib.parameters` module.
Also see `UsesParams._filter_params_by_context()`.
"""
attr = getattr(self, name)
if isinstance(attr, (Param, str)):
return (attr,)
if callable(attr):
return attr()
return attr
def _filter_params_by_context(self, name, env=None):
"""
Filter params on attribute named ``name`` by environment ``env``.
For example:
>>> from ipalib.config import Env
>>> class Example(UsesParams):
... takes_args = (
... Str('foo_only', include=['foo']),
... Str('not_bar', exclude=['bar']),
... 'both',
... )
...
>>> eg = Example()
>>> foo = Env(context='foo')
>>> bar = Env(context='bar')
>>> another = Env(context='another')
>>> (foo.context, bar.context, another.context)
('foo', 'bar', 'another')
>>> list(eg._filter_params_by_context('takes_args', foo))
[Str('foo_only', include=['foo']), Str('not_bar', exclude=['bar']), Str('both')]
>>> list(eg._filter_params_by_context('takes_args', bar))
[Str('both')]
>>> list(eg._filter_params_by_context('takes_args', another))
[Str('not_bar', exclude=['bar']), Str('both')]
"""
env = getattr(self, 'env', env)
for spec in self._get_params_iterable(name):
param = create_param(spec)
if env is None or param.use_in_context(env):
yield param
class Command(plugable.Plugin):
"""
A public IPA atomic operation.

View File

@ -232,7 +232,8 @@ class Param(ReadOnly):
('autofill', bool, False),
('query', bool, False),
('attribute', bool, False),
('limit_to', frozenset, None),
('include', frozenset, None),
('exclude', frozenset, None),
('flags', frozenset, frozenset()),
# The 'default' kwarg gets appended in Param.__init__():
@ -328,6 +329,16 @@ class Param(ReadOnly):
else:
self._get_default = None
# 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,
)
)
# Check that all the rules are callable
self.class_rules = tuple(class_rules)
self.rules = rules
@ -345,12 +356,18 @@ class Param(ReadOnly):
"""
Return an expresion that could construct this `Param` instance.
"""
return make_repr(
return '%s(%s)' % (
self.__class__.__name__,
self.param_spec,
**self.__kw
', '.join(self.__repr_iter())
)
def __repr_iter(self):
yield repr(self.param_spec)
for rule in self.rules:
yield rule.__name__
for key in sorted(self.__kw):
yield '%s=%r' % (key, self.__kw[key])
def __call__(self, value, **kw):
"""
One stop shopping.
@ -362,6 +379,33 @@ class Param(ReadOnly):
self.validate(value)
return value
def use_in_context(self, env):
"""
Return ``True`` if this param should be used in ``env.context``.
For example:
>>> from ipalib.config import Env
>>> server = Env()
>>> server.context = 'server'
>>> client = Env()
>>> client.context = 'client'
>>> param = Param('my_param', include=['server', 'webui'])
>>> param.use_in_context(server)
True
>>> param.use_in_context(client)
False
So that a subclass can add additional logic basic on other environment
variables, the `config.Env` instance is passed in rather than just the
value of ``env.context``.
"""
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
def safe_value(self, value):
"""
Return a value safe for logging.

View File

@ -27,7 +27,7 @@ from inspect import isclass
from tests.util import raises, ClassChecker, read_only
from tests.util import dummy_ugettext, assert_equal
from tests.data import binary_bytes, utf8_bytes, unicode_str
from ipalib import parameters, request, errors
from ipalib import parameters, request, errors, config
from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS
@ -168,7 +168,8 @@ class test_Param(ClassChecker):
assert o.autofill is False
assert o.query is False
assert o.attribute is False
assert o.limit_to is None
assert o.include is None
assert o.exclude is None
assert o.flags == frozenset()
# Test that ValueError is raised when a kwarg from a subclass
@ -223,6 +224,18 @@ class test_Param(ClassChecker):
"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',
include=['server', 'foo'],
exclude=['client', 'bar'],
)
assert str(e) == '%s: cannot have both %s=%r and %s=%r' % (
"Param('my_param')",
'include', frozenset(['server', 'foo']),
'exclude', frozenset(['client', 'bar']),
)
# Test that _get_default gets set:
call1 = lambda first, last: first[0] + last
call2 = lambda **kw: 'The Default'
@ -245,6 +258,28 @@ class test_Param(ClassChecker):
o = self.cls('name', multivalue=True)
assert repr(o) == "Param('name', multivalue=True)"
def test_use_in_context(self):
"""
Test the `ipalib.parameters.Param.use_in_context` method.
"""
set1 = ('one', 'two', 'three')
set2 = ('four', 'five', 'six')
param1 = self.cls('param1')
param2 = self.cls('param2', include=set1)
param3 = self.cls('param3', exclude=set2)
for context in set1:
env = config.Env()
env.context = context
assert param1.use_in_context(env) is True, context
assert param2.use_in_context(env) is True, context
assert param3.use_in_context(env) is True, context
for context in set2:
env = config.Env()
env.context = context
assert param1.use_in_context(env) is True, context
assert param2.use_in_context(env) is False, context
assert param3.use_in_context(env) is False, context
def test_safe_value(self):
"""
Test the `ipalib.parameters.Param.safe_value` method.