mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Reorganized Command methods so it is easier to understand and added lots of docstrings
This commit is contained in:
parent
bc9edbfdf6
commit
b7fe92f44f
@ -449,6 +449,27 @@ def create_param(spec):
|
|||||||
|
|
||||||
|
|
||||||
class Command(plugable.Plugin):
|
class Command(plugable.Plugin):
|
||||||
|
"""
|
||||||
|
A public IPA atomic operation.
|
||||||
|
|
||||||
|
All plugins that subclass from `Command` will be automatically available
|
||||||
|
as a CLI command and as an XML-RPC method.
|
||||||
|
|
||||||
|
Plugins that subclass from Command are registered in the ``api.Command``
|
||||||
|
namespace. For example:
|
||||||
|
|
||||||
|
>>> api = plugable.API(Command)
|
||||||
|
>>> class my_command(Command):
|
||||||
|
... pass
|
||||||
|
...
|
||||||
|
>>> api.register(my_command)
|
||||||
|
>>> api.finalize()
|
||||||
|
>>> list(api.Command)
|
||||||
|
['my_command']
|
||||||
|
>>> api.Command.my_command
|
||||||
|
PluginProxy(Command, __main__.my_command())
|
||||||
|
"""
|
||||||
|
|
||||||
__public__ = frozenset((
|
__public__ = frozenset((
|
||||||
'get_default',
|
'get_default',
|
||||||
'convert',
|
'convert',
|
||||||
@ -468,25 +489,231 @@ class Command(plugable.Plugin):
|
|||||||
options = None
|
options = None
|
||||||
params = None
|
params = None
|
||||||
|
|
||||||
|
def __call__(self, *args, **kw):
|
||||||
|
"""
|
||||||
|
Perform validation and then execute the command.
|
||||||
|
|
||||||
|
If not in a server context, the call will be forwarded over
|
||||||
|
XML-RPC and the executed an the nearest IPA server.
|
||||||
|
"""
|
||||||
|
if len(args) > 0:
|
||||||
|
arg_kw = self.args_to_kw(*args)
|
||||||
|
assert set(arg_kw).intersection(kw) == set()
|
||||||
|
kw.update(arg_kw)
|
||||||
|
kw = self.normalize(**kw)
|
||||||
|
kw = self.convert(**kw)
|
||||||
|
kw.update(self.get_default(**kw))
|
||||||
|
self.validate(**kw)
|
||||||
|
args = tuple(kw.pop(name) for name in self.args)
|
||||||
|
return self.run(*args, **kw)
|
||||||
|
|
||||||
|
def args_to_kw(self, *values):
|
||||||
|
"""
|
||||||
|
Map positional into keyword arguments.
|
||||||
|
"""
|
||||||
|
if self.max_args is not None and len(values) > self.max_args:
|
||||||
|
if self.max_args == 0:
|
||||||
|
raise errors.ArgumentError(self, 'takes no arguments')
|
||||||
|
if self.max_args == 1:
|
||||||
|
raise errors.ArgumentError(self, 'takes at most 1 argument')
|
||||||
|
raise errors.ArgumentError(self,
|
||||||
|
'takes at most %d arguments' % len(self.args)
|
||||||
|
)
|
||||||
|
return dict(self.__args_to_kw_iter(values))
|
||||||
|
|
||||||
|
def __args_to_kw_iter(self, values):
|
||||||
|
"""
|
||||||
|
Generator used by `Command.args_to_kw` method.
|
||||||
|
"""
|
||||||
|
multivalue = False
|
||||||
|
for (i, arg) in enumerate(self.args()):
|
||||||
|
assert not multivalue
|
||||||
|
if len(values) > i:
|
||||||
|
if arg.multivalue:
|
||||||
|
multivalue = True
|
||||||
|
yield (arg.name, values[i:])
|
||||||
|
else:
|
||||||
|
yield (arg.name, values[i])
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def kw_to_args(self, **kw):
|
||||||
|
"""
|
||||||
|
Map keyword into positional arguments.
|
||||||
|
"""
|
||||||
|
return tuple(kw.get(name, None) for name in self.args)
|
||||||
|
|
||||||
|
def normalize(self, **kw):
|
||||||
|
"""
|
||||||
|
Return a dictionary of normalized values.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> class my_command(Command):
|
||||||
|
... takes_options = (
|
||||||
|
... Param('first', normalize=lambda value: value.lower()),
|
||||||
|
... Param('last'),
|
||||||
|
... )
|
||||||
|
...
|
||||||
|
>>> c = my_command()
|
||||||
|
>>> c.finalize()
|
||||||
|
>>> c.normalize(first='JOHN', last='DOE')
|
||||||
|
{'last': 'DOE', 'first': 'john'}
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
(k, self.params[k].normalize(v)) for (k, v) in kw.iteritems()
|
||||||
|
)
|
||||||
|
|
||||||
|
def convert(self, **kw):
|
||||||
|
"""
|
||||||
|
Return a dictionary of values converted to correct type.
|
||||||
|
|
||||||
|
>>> from ipalib import ipa_types
|
||||||
|
>>> class my_command(Command):
|
||||||
|
... takes_args = (
|
||||||
|
... Param('one', type=ipa_types.Int()),
|
||||||
|
... 'two',
|
||||||
|
... )
|
||||||
|
...
|
||||||
|
>>> c = my_command()
|
||||||
|
>>> c.finalize()
|
||||||
|
>>> c.convert(one=1, two=2)
|
||||||
|
{'two': u'2', 'one': 1}
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
(k, self.params[k].convert(v)) for (k, v) in kw.iteritems()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_default(self, **kw):
|
||||||
|
"""
|
||||||
|
Return a dictionary of defaults for all missing required values.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
>>> class my_command(Command):
|
||||||
|
... takes_args = [Param('color', default='Red')]
|
||||||
|
...
|
||||||
|
>>> c = my_command()
|
||||||
|
>>> c.finalize()
|
||||||
|
>>> c.get_default()
|
||||||
|
{'color': 'Red'}
|
||||||
|
>>> c.get_default(color='Yellow')
|
||||||
|
{}
|
||||||
|
"""
|
||||||
|
return dict(self.__get_default_iter(kw))
|
||||||
|
|
||||||
|
def __get_default_iter(self, kw):
|
||||||
|
"""
|
||||||
|
Generator method used by `Command.get_default`.
|
||||||
|
"""
|
||||||
|
for param in self.params():
|
||||||
|
if param.required and kw.get(param.name, None) is None:
|
||||||
|
yield (param.name, param.get_default(**kw))
|
||||||
|
|
||||||
|
def validate(self, **kw):
|
||||||
|
"""
|
||||||
|
Validate all values.
|
||||||
|
|
||||||
|
If any value fails the validation, `ipalib.errors.ValidationError`
|
||||||
|
(or a subclass thereof) will be raised.
|
||||||
|
"""
|
||||||
|
for param in self.params():
|
||||||
|
value = kw.get(param.name, None)
|
||||||
|
if value is not None:
|
||||||
|
param.validate(value)
|
||||||
|
elif param.required:
|
||||||
|
raise errors.RequirementError(param.name)
|
||||||
|
|
||||||
|
def run(self, *args, **kw):
|
||||||
|
"""
|
||||||
|
Dispatch to `Command.execute` or `Command.forward`.
|
||||||
|
|
||||||
|
If running in a server context, `Command.execute` is called and the
|
||||||
|
actually work this command performs is executed locally.
|
||||||
|
|
||||||
|
If running in a non-server context, `Command.forward` is called,
|
||||||
|
which forwards this call over XML-RPC to the exact same command
|
||||||
|
on the nearest IPA server and the actual work this command
|
||||||
|
performs is executed remotely.
|
||||||
|
"""
|
||||||
|
if self.api.env.server_context:
|
||||||
|
target = self.execute
|
||||||
|
else:
|
||||||
|
target = self.forward
|
||||||
|
object.__setattr__(self, 'run', target)
|
||||||
|
return target(*args, **kw)
|
||||||
|
|
||||||
|
def execute(self, *args, **kw):
|
||||||
|
"""
|
||||||
|
Perform the actual work this command does.
|
||||||
|
|
||||||
|
This method should be implemented only against functionality
|
||||||
|
in self.api.Backend. For example, a hypothetical
|
||||||
|
user_add.execute() might be implemented like this:
|
||||||
|
|
||||||
|
>>> class user_add(Command):
|
||||||
|
... def execute(self, **kw):
|
||||||
|
... return self.api.Backend.ldap.add(**kw)
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
print '%s.execute():' % self.name
|
||||||
|
print ' args =', args
|
||||||
|
print ' kw =', kw
|
||||||
|
|
||||||
|
def forward(self, *args, **kw):
|
||||||
|
"""
|
||||||
|
Forward call over XML-RPC to this same command on server.
|
||||||
|
"""
|
||||||
|
return self.api.Backend.xmlrpc.forward_call(self.name, *args, **kw)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
|
"""
|
||||||
|
Finalize plugin initialization.
|
||||||
|
|
||||||
|
This method creates the ``args``, ``options``, and ``params``
|
||||||
|
namespaces. This is not done in `Command.__init__` because
|
||||||
|
subclasses (like `crud.Add`) might need to access other plugins
|
||||||
|
loaded in self.api to determine what their custom `Command.get_args`
|
||||||
|
and `Command.get_options` methods should yield.
|
||||||
|
"""
|
||||||
self.args = plugable.NameSpace(self.__create_args(), sort=False)
|
self.args = plugable.NameSpace(self.__create_args(), sort=False)
|
||||||
if len(self.args) == 0 or not self.args[-1].multivalue:
|
if len(self.args) == 0 or not self.args[-1].multivalue:
|
||||||
self.max_args = len(self.args)
|
self.max_args = len(self.args)
|
||||||
else:
|
else:
|
||||||
self.max_args = None
|
self.max_args = None
|
||||||
self.options = plugable.NameSpace(self.__create_options(), sort=False)
|
self.options = plugable.NameSpace(
|
||||||
|
(create_param(spec) for spec in self.get_options()),
|
||||||
|
sort=False
|
||||||
|
)
|
||||||
self.params = plugable.NameSpace(
|
self.params = plugable.NameSpace(
|
||||||
tuple(self.args()) + tuple(self.options()), sort=False
|
tuple(self.args()) + tuple(self.options()), sort=False
|
||||||
)
|
)
|
||||||
super(Command, self).finalize()
|
super(Command, self).finalize()
|
||||||
|
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
|
"""
|
||||||
|
Return iterable with arguments for Command.args namespace.
|
||||||
|
|
||||||
|
Subclasses can override this to customize how the arguments
|
||||||
|
are determined. For an example of why this can be useful,
|
||||||
|
see `ipalib.crud.Mod`.
|
||||||
|
"""
|
||||||
return self.takes_args
|
return self.takes_args
|
||||||
|
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
|
"""
|
||||||
|
Return iterable with options for Command.options namespace.
|
||||||
|
|
||||||
|
Subclasses can override this to customize how the options
|
||||||
|
are determined. For an example of why this can be useful,
|
||||||
|
see `ipalib.crud.Mod`.
|
||||||
|
"""
|
||||||
return self.takes_options
|
return self.takes_options
|
||||||
|
|
||||||
def __create_args(self):
|
def __create_args(self):
|
||||||
|
"""
|
||||||
|
Generator used to create args namespace.
|
||||||
|
"""
|
||||||
optional = False
|
optional = False
|
||||||
multivalue = False
|
multivalue = False
|
||||||
for arg in self.get_args():
|
for arg in self.get_args():
|
||||||
@ -505,95 +732,6 @@ class Command(plugable.Plugin):
|
|||||||
multivalue = True
|
multivalue = True
|
||||||
yield arg
|
yield arg
|
||||||
|
|
||||||
def __create_options(self):
|
|
||||||
for option in self.get_options():
|
|
||||||
yield create_param(option)
|
|
||||||
|
|
||||||
def convert(self, **kw):
|
|
||||||
return dict(
|
|
||||||
(k, self.params[k].convert(v)) for (k, v) in kw.iteritems()
|
|
||||||
)
|
|
||||||
|
|
||||||
def normalize(self, **kw):
|
|
||||||
return dict(
|
|
||||||
(k, self.params[k].normalize(v)) for (k, v) in kw.iteritems()
|
|
||||||
)
|
|
||||||
|
|
||||||
def __get_default_iter(self, kw):
|
|
||||||
for param in self.params():
|
|
||||||
if param.required and kw.get(param.name, None) is None:
|
|
||||||
yield (param.name, param.get_default(**kw))
|
|
||||||
|
|
||||||
def get_default(self, **kw):
|
|
||||||
return dict(self.__get_default_iter(kw))
|
|
||||||
|
|
||||||
def validate(self, **kw):
|
|
||||||
for param in self.params():
|
|
||||||
value = kw.get(param.name, None)
|
|
||||||
if value is not None:
|
|
||||||
param.validate(value)
|
|
||||||
elif param.required:
|
|
||||||
raise errors.RequirementError(param.name)
|
|
||||||
|
|
||||||
def execute(self, *args, **kw):
|
|
||||||
print '%s.execute():' % self.name
|
|
||||||
print ' args =', args
|
|
||||||
print ' kw =', kw
|
|
||||||
|
|
||||||
def forward(self, *args, **kw):
|
|
||||||
"""
|
|
||||||
Forward call over XML-RPC.
|
|
||||||
"""
|
|
||||||
return self.api.Backend.xmlrpc.forward_call(self.name, *args, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
def __call__(self, *args, **kw):
|
|
||||||
if len(args) > 0:
|
|
||||||
arg_kw = self.args_to_kw(*args)
|
|
||||||
assert set(arg_kw).intersection(kw) == set()
|
|
||||||
kw.update(arg_kw)
|
|
||||||
kw = self.normalize(**kw)
|
|
||||||
kw = self.convert(**kw)
|
|
||||||
kw.update(self.get_default(**kw))
|
|
||||||
self.validate(**kw)
|
|
||||||
args = tuple(kw.pop(name) for name in self.args)
|
|
||||||
return self.run(*args, **kw)
|
|
||||||
|
|
||||||
def run(self, *args, **kw):
|
|
||||||
if self.api.env.server_context:
|
|
||||||
target = self.execute
|
|
||||||
else:
|
|
||||||
target = self.forward
|
|
||||||
object.__setattr__(self, 'run', target)
|
|
||||||
return target(*args, **kw)
|
|
||||||
|
|
||||||
def args_to_kw(self, *values):
|
|
||||||
if self.max_args is not None and len(values) > self.max_args:
|
|
||||||
if self.max_args == 0:
|
|
||||||
raise errors.ArgumentError(self, 'takes no arguments')
|
|
||||||
if self.max_args == 1:
|
|
||||||
raise errors.ArgumentError(self, 'takes at most 1 argument')
|
|
||||||
raise errors.ArgumentError(self,
|
|
||||||
'takes at most %d arguments' % len(self.args)
|
|
||||||
)
|
|
||||||
return dict(self.__args_to_kw_iter(values))
|
|
||||||
|
|
||||||
def __args_to_kw_iter(self, values):
|
|
||||||
multivalue = False
|
|
||||||
for (i, arg) in enumerate(self.args()):
|
|
||||||
assert not multivalue
|
|
||||||
if len(values) > i:
|
|
||||||
if arg.multivalue:
|
|
||||||
multivalue = True
|
|
||||||
yield (arg.name, values[i:])
|
|
||||||
else:
|
|
||||||
yield (arg.name, values[i])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
def kw_to_args(self, **kw):
|
|
||||||
return tuple(kw.get(name, None) for name in self.args)
|
|
||||||
|
|
||||||
|
|
||||||
class Object(plugable.Plugin):
|
class Object(plugable.Plugin):
|
||||||
__public__ = frozenset((
|
__public__ = frozenset((
|
||||||
|
Loading…
Reference in New Issue
Block a user