0

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.

Aidenhjj
  • 1,249
  • 1
  • 14
  • 27

2 Answers2

0

Your decorator apparently registers a function to be called later. As such, it's completely inappropriate for use on a class method, since it will have no idea of which instance of the class to invoke the method on.

The only way you'd be able to do this would be to manually register a bound method from a particular class instance - this cannot be done using the decorator syntax. For example, put this somewhere after the definition of your class:

panelOpenHandler(context.controls.PerformanceTuneDemoPanel)(Test().callbackOpen)
jasonharper
  • 9,450
  • 2
  • 18
  • 42
  • thanks, makes sense; but this is now giving me the following error; `AttributeError: 'instancemethod' object has no attribute 'panelOpenHandler'` – Aidenhjj Aug 03 '17 at 14:56
  • I don't think you copied the code correctly (it's supposed to be a standalone statement) - nothing should be looking up `panelOpenHandler` as an attribute. – jasonharper Aug 03 '17 at 15:00
  • I guess it's an operation being performed inside the `panelOpenHandler` decorator. I copied your code correctly. – Aidenhjj Aug 03 '17 at 15:03
0

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.

Aidenhjj
  • 1,249
  • 1
  • 14
  • 27