Search code examples
pythonpython-2.7jython

Python: Using API Event Handlers with OOP


I am trying to build some UI panels for an Eclipse based tool. The API for the tool has a mechanism for event handling based on decorators, so for example, the following ties callbackOpen to the opening of a_panel_object:

@panelOpenHandler(a_panel_object)
def callbackOpen(event):
    print "opening HERE!!"

This works fine, but I wanted to wrap all of my event handlers and actual data processing for the panel behind a class. Ideally I would like to do something like:

class Test(object):
    def __init__(self):
        # initialise some data here

    @panelOpenHandler(a_panel_object)
    def callbackOpen(self, event):
        print "opening HERE!!"

But this doesn't work, I think probably because I am giving it a callback that takes both self and event, when the decorator is only supplying event when it calls the function internally (note: I have no access to source code on panelOpenHandler, and it is not very well documented...also, any error messages are getting swallowed by Eclipse / jython somewhere).

Is there any way that I can use a library decorator that provides one argument to the function being decorated on a function that takes more than one argument? Can I use lambdas in some way to bind the self argument and make it implicit?

I've tried to incorporate some variation of the approaches here and here, but I don't think that it's quite the same problem.


Solution

  • I found a work around for this problem. I'm not sure if there is a more elegant solution, but basically the problem boiled down to having to expose a callback function to global() scope, and then decorate it with the API decorator using f()(g) syntax.

    Therefore, I wrote a base class (CallbackRegisterer), which offers the bindHandler() method to any derived classes - this method wraps a function and gives it a unique id per instance of CallbackRegisterer (I am opening a number of UI Panels at the same time):

    class CallbackRegisterer(object):
    
        __count = 0
    
        @classmethod
        def _instanceCounter(cls):
            CallbackRegisterer.__count += 1
            return CallbackRegisterer.__count
    
        def __init__(self):
            """
            Constructor
            @param eq_instance 0=playback 1=record 2=sidetone.
            """
            self._id = self._instanceCounter()
            print "instantiating #%d instance of %s" % (self._id, self._getClassName())
    
    
        def bindHandler(self, ui_element, callback, callback_args = [], handler_type = None, 
                                    initialize = False, forward_event_args = False, handler_id = None):
    
            proxy = lambda *args: self._handlerProxy(callback, args, callback_args, forward_event_args)
            handler_name = callback.__name__ + "_" + str(self._id)
            if handler_id is not None:
                handler_name += "_" + str(handler_id)
            globals()[handler_name] = proxy
    
            # print "handler_name: %s" % handler_name
    
            handler_type(ui_element)(proxy)
            if initialize:
                proxy()
    
        def _handlerProxy(self, callback, event_args, callback_args, forward_event_args):
            try:
                if forward_event_args:
                    new_args = [x for x in event_args]
                    new_args.extend(callback_args)
                    callback(*new_args)
                else:
                    callback(*callback_args)
            except:
                print "exception in callback???"
                self.log.exception('In event callback')
                raise
    
        def _getClassName(self):
            return self.__class__.__name__
    

    I can then derive a class from this and pass in my callback, which will be correctly decorated using the API decorator:

    class Panel(CallbackRegisterer):
        def __init__(self):
    
            super(Panel, self).__init__()
    
            # can bind from sub classes of Panel as well - different class name in handle_name
            self.bindHandler(self.controls.test_button, self._testButtonCB, handler_type = valueChangeHandler)
    
            # can bind multiple versions of same function for repeated ui elements, etc.
            for idx in range(0, 10):
                self.bindHandler(self.controls["check_box_"+str(idx)], self._testCheckBoxCB, 
                            callback_args = [idx], handler_type = valueChangeHandler, handler_id = idx)
    
        def _testCheckBoxCB(self, *args):
            check_box_id = args[0]
            print "in _testCheckBoxCB #%d" % check_box_id
    
        def _testButtonCB(self):
            """
            Handler for test button
            """
            print "in _testButtonCB"
    
    
    panel = Panel()
    

    Note, that I can also derive further sub-classes from Panel, and any callbacks bound there will get their own unique handler_name, based on class name string.