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 return context.current_frame
_callback_registry = {}
class Command(HasParam): class Command(HasParam):
""" """
A public IPA atomic operation. A public IPA atomic operation.
@ -390,6 +393,14 @@ class Command(HasParam):
['my_command'] ['my_command']
>>> api.Command.my_command # doctest:+ELLIPSIS >>> api.Command.my_command # doctest:+ELLIPSIS
ipalib.frontend.my_command() 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 finalize_early = False
@ -414,6 +425,8 @@ class Command(HasParam):
msg_summary = None msg_summary = None
msg_truncated = _('Results are truncated, try a more specific search') msg_truncated = _('Results are truncated, try a more specific search')
callback_types = ()
def __call__(self, *args, **options): def __call__(self, *args, **options):
""" """
Perform validation and then execute the command. Perform validation and then execute the command.
@ -1088,6 +1101,46 @@ class Command(HasParam):
return json_dict 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): class LocalOrRemote(Command):
""" """
A command that is explicitly executed locally or remotely. A command that is explicitly executed locally or remotely.

View File

@ -28,7 +28,7 @@ import base64
import six import six
from ipalib import api, crud, errors 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 import Flag, Int, Str
from ipalib.cli import to_cli from ipalib.cli import to_cli
from ipalib import output from ipalib import output
@ -865,59 +865,7 @@ def _check_limit_object_class(attributes, attrs, allow_only):
attribute=limitattrs[0])) attribute=limitattrs[0]))
class CallbackInterface(Method): class BaseLDAPCommand(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):
""" """
Base class for Base LDAP Commands. Base class for Base LDAP Commands.
""" """
@ -940,7 +888,10 @@ last, after all sets and adds."""),
exclude='webui', 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): def get_summary_default(self, output):
if 'value' in output: if 'value' in output:

View File

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