frontend: merge baseldap.CallbackRegistry into Command

Also make it possible for subclasses to introduce new callback types.

https://fedorahosted.org/freeipa/ticket/4739

Reviewed-By: David Kupka <dkupka@redhat.com>
This commit is contained in:
Jan Cholasta 2016-04-27 09:34:04 +02:00
parent a30bc8a351
commit 60fa6ed444
3 changed files with 63 additions and 58 deletions

View File

@ -369,6 +369,9 @@ class HasParam(Plugin):
return context.current_frame
_callback_registry = {}
class Command(HasParam):
"""
A public IPA atomic operation.
@ -390,6 +393,14 @@ class Command(HasParam):
['my_command']
>>> api.Command.my_command # doctest:+ELLIPSIS
ipalib.frontend.my_command()
This class's subclasses allow different types of callbacks to be added and
removed to them.
Registering a callback is done either by ``register_callback``, or by
defining a ``<type>_callback`` method.
Subclasses should define the `callback_types` attribute as a tuple of
allowed callback types.
"""
finalize_early = False
@ -414,6 +425,8 @@ class Command(HasParam):
msg_summary = None
msg_truncated = _('Results are truncated, try a more specific search')
callback_types = ()
def __call__(self, *args, **options):
"""
Perform validation and then execute the command.
@ -1088,6 +1101,46 @@ class Command(HasParam):
return json_dict
@classmethod
def get_callbacks(cls, callback_type):
"""Yield callbacks of the given type"""
# Use one shared callback registry, keyed on class, to avoid problems
# with missing attributes being looked up in superclasses
callbacks = _callback_registry.get(callback_type, {}).get(cls, [None])
for callback in callbacks:
if callback is None:
try:
yield getattr(cls, '%s_callback' % callback_type)
except AttributeError:
pass
else:
yield callback
@classmethod
def register_callback(cls, callback_type, callback, first=False):
"""Register a callback
:param callback_type: The callback type (e.g. 'pre', 'post')
:param callback: The callable added
:param first: If true, the new callback will be added before all
existing callbacks; otherwise it's added after them
Note that callbacks registered this way will be attached to this class
only, not to its subclasses.
"""
assert callback_type in cls.callback_types
assert callable(callback)
_callback_registry.setdefault(callback_type, {})
try:
callbacks = _callback_registry[callback_type][cls]
except KeyError:
callbacks = _callback_registry[callback_type][cls] = [None]
if first:
callbacks.insert(0, callback)
else:
callbacks.append(callback)
class LocalOrRemote(Command):
"""
A command that is explicitly executed locally or remotely.

View File

@ -28,7 +28,7 @@ import base64
import six
from ipalib import api, crud, errors
from ipalib import Method, Object, Command
from ipalib import Method, Object
from ipalib import Flag, Int, Str
from ipalib.cli import to_cli
from ipalib import output
@ -865,59 +865,7 @@ def _check_limit_object_class(attributes, attrs, allow_only):
attribute=limitattrs[0]))
class CallbackInterface(Method):
"""Callback registration interface
This class's subclasses allow different types of callbacks to be added and
removed to them.
Registering a callback is done either by ``register_callback``, or by
defining a ``<type>_callback`` method.
Subclasses should define the `_callback_registry` attribute as a dictionary
mapping allowed callback types to (initially) empty dictionaries.
"""
_callback_registry = dict()
@classmethod
def get_callbacks(cls, callback_type):
"""Yield callbacks of the given type"""
# Use one shared callback registry, keyed on class, to avoid problems
# with missing attributes being looked up in superclasses
callbacks = cls._callback_registry[callback_type].get(cls, [None])
for callback in callbacks:
if callback is None:
try:
yield getattr(cls, '%s_callback' % callback_type)
except AttributeError:
pass
else:
yield callback
@classmethod
def register_callback(cls, callback_type, callback, first=False):
"""Register a callback
:param callback_type: The callback type (e.g. 'pre', 'post')
:param callback: The callable added
:param first: If true, the new callback will be added before all
existing callbacks; otherwise it's added after them
Note that callbacks registered this way will be attached to this class
only, not to its subclasses.
"""
assert callable(callback)
try:
callbacks = cls._callback_registry[callback_type][cls]
except KeyError:
callbacks = cls._callback_registry[callback_type][cls] = [None]
if first:
callbacks.insert(0, callback)
else:
callbacks.append(callback)
class BaseLDAPCommand(CallbackInterface, Command):
class BaseLDAPCommand(Method):
"""
Base class for Base LDAP Commands.
"""
@ -940,7 +888,10 @@ last, after all sets and adds."""),
exclude='webui',
)
_callback_registry = dict(pre={}, post={}, exc={}, interactive_prompt={})
callback_types = Method.callback_types + ('pre',
'post',
'exc',
'interactive_prompt')
def get_summary_default(self, output):
if 'value' in output:

View File

@ -26,6 +26,7 @@ import ldap
from ipapython.dn import DN
from ipapython import ipaldap
from ipalib import errors
from ipalib.frontend import Command
from ipalib.plugins import baseldap
from ipatests.util import assert_deepequal
import pytest
@ -33,7 +34,7 @@ import pytest
@pytest.mark.tier0
def test_exc_wrapper():
"""Test the CallbackInterface._exc_wrapper helper method"""
"""Test the BaseLDAPCommand._exc_wrapper helper method"""
handled_exceptions = []
class test_callback(baseldap.BaseLDAPCommand):
@ -77,8 +78,8 @@ def test_exc_wrapper():
@pytest.mark.tier0
def test_callback_registration():
class callbacktest_base(baseldap.CallbackInterface):
_callback_registry = dict(test={})
class callbacktest_base(Command):
callback_types = Command.callback_types + ('test',)
def test_callback(self, param):
messages.append(('Base test_callback', param))