mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
parent
86472a94ee
commit
4f9224774f
@ -198,9 +198,11 @@ class Env(object):
|
|||||||
|
|
||||||
__locked = False
|
__locked = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, **initialize):
|
||||||
object.__setattr__(self, '_Env__d', {})
|
object.__setattr__(self, '_Env__d', {})
|
||||||
object.__setattr__(self, '_Env__done', set())
|
object.__setattr__(self, '_Env__done', set())
|
||||||
|
if initialize:
|
||||||
|
self._merge(**initialize)
|
||||||
|
|
||||||
def __lock__(self):
|
def __lock__(self):
|
||||||
"""
|
"""
|
||||||
|
@ -25,7 +25,7 @@ import re
|
|||||||
import inspect
|
import inspect
|
||||||
import plugable
|
import plugable
|
||||||
from plugable import lock, check_name
|
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 util import make_repr
|
||||||
|
|
||||||
from errors import ZeroArgumentError, MaxArgumentError, OverlapError, RequiresRoot
|
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
|
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):
|
class Command(plugable.Plugin):
|
||||||
"""
|
"""
|
||||||
A public IPA atomic operation.
|
A public IPA atomic operation.
|
||||||
|
@ -232,7 +232,8 @@ class Param(ReadOnly):
|
|||||||
('autofill', bool, False),
|
('autofill', bool, False),
|
||||||
('query', bool, False),
|
('query', bool, False),
|
||||||
('attribute', bool, False),
|
('attribute', bool, False),
|
||||||
('limit_to', frozenset, None),
|
('include', frozenset, None),
|
||||||
|
('exclude', frozenset, None),
|
||||||
('flags', frozenset, frozenset()),
|
('flags', frozenset, frozenset()),
|
||||||
|
|
||||||
# The 'default' kwarg gets appended in Param.__init__():
|
# The 'default' kwarg gets appended in Param.__init__():
|
||||||
@ -328,6 +329,16 @@ class Param(ReadOnly):
|
|||||||
else:
|
else:
|
||||||
self._get_default = None
|
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
|
# Check that all the rules are callable
|
||||||
self.class_rules = tuple(class_rules)
|
self.class_rules = tuple(class_rules)
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
@ -345,12 +356,18 @@ class Param(ReadOnly):
|
|||||||
"""
|
"""
|
||||||
Return an expresion that could construct this `Param` instance.
|
Return an expresion that could construct this `Param` instance.
|
||||||
"""
|
"""
|
||||||
return make_repr(
|
return '%s(%s)' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self.param_spec,
|
', '.join(self.__repr_iter())
|
||||||
**self.__kw
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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):
|
def __call__(self, value, **kw):
|
||||||
"""
|
"""
|
||||||
One stop shopping.
|
One stop shopping.
|
||||||
@ -362,6 +379,33 @@ class Param(ReadOnly):
|
|||||||
self.validate(value)
|
self.validate(value)
|
||||||
return 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):
|
def safe_value(self, value):
|
||||||
"""
|
"""
|
||||||
Return a value safe for logging.
|
Return a value safe for logging.
|
||||||
|
@ -27,7 +27,7 @@ from inspect import isclass
|
|||||||
from tests.util import raises, ClassChecker, read_only
|
from tests.util import raises, ClassChecker, read_only
|
||||||
from tests.util import dummy_ugettext, assert_equal
|
from tests.util import dummy_ugettext, assert_equal
|
||||||
from tests.data import binary_bytes, utf8_bytes, unicode_str
|
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
|
from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS
|
||||||
|
|
||||||
|
|
||||||
@ -168,7 +168,8 @@ class test_Param(ClassChecker):
|
|||||||
assert o.autofill is False
|
assert o.autofill is False
|
||||||
assert o.query is False
|
assert o.query is False
|
||||||
assert o.attribute 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()
|
assert o.flags == frozenset()
|
||||||
|
|
||||||
# Test that ValueError is raised when a kwarg from a subclass
|
# 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',
|
"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:
|
# Test that _get_default gets set:
|
||||||
call1 = lambda first, last: first[0] + last
|
call1 = lambda first, last: first[0] + last
|
||||||
call2 = lambda **kw: 'The Default'
|
call2 = lambda **kw: 'The Default'
|
||||||
@ -245,6 +258,28 @@ class test_Param(ClassChecker):
|
|||||||
o = self.cls('name', multivalue=True)
|
o = self.cls('name', multivalue=True)
|
||||||
assert repr(o) == "Param('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):
|
def test_safe_value(self):
|
||||||
"""
|
"""
|
||||||
Test the `ipalib.parameters.Param.safe_value` method.
|
Test the `ipalib.parameters.Param.safe_value` method.
|
||||||
|
Loading…
Reference in New Issue
Block a user