Search code examples
pythonpython-3.xpropertieswin32compython-decorators

Decorate each method of win32com COM object


Python 3
I have a COM object via win32com.client.Dispatch with many methods used to automate the application. I'd like it so that every time any method of the COM object is called, python does something based on the returned value (logging actually).
My automation pseudocode is like this:

obj = win32com.client.DispatchEx("3rdParty.Application")
obj.methodA(Parameter)
obj.methodB()
obj.non_method_call
obj.methodN()
...

When any method call is made to obj, I would like what's intended by this pseudocode to happen:

x = obj.any_method(*args)
if x:
    logger.debug(any_method.__name__ + ' was called at ' + datetime.now().strftime("%H:%M") + ' with parameters ' + str(*args)
else:
    logger.error(obj.cerrmsg)
    obj.logout

Note that the any_method.__name__ part would be nice, but is not crucial. Can python do this, especially with a COM object, and without writing the logic for a finite set of methods?
Decorators sounded right because they modify functions/classes, but posts I've reviewed wouldn't work in this case (like going through the object's method dict), and then I heard they only work on methods defined in my own code. @Property was suggested, but I couldn't work out how to apply it in this case.
Advanced tricks welcome (getattr, metaclasses, functools, wraps, etc.) but please demonstrate.


Solution

  • I tested the approach I mentioned in my comment, against win32com.client and hit a basic problem:

    The recursive nature of self.__getattribute__ means that trying to store an object and then use self.stored_obj fails because it calls __getattribute__ recursively forever.

    If you excuse the awful use of a global variable as a hackish way around this, and meaning you can only use this once at a time, it basically works.

    i.e. it might be OK for debugging/tracing what's happening with your COM object, but it's not a nice or robust solution:

    global _wrapped_object
    
    class LogWrap:
        def __init__(self, object):
            global _wrapped_object
            _wrapped_object = object
    
        def __getattribute__(self, name):
            global _wrapped_object
    
            next_hop_attr = _wrapped_object.__getattr__(name)
    
            if callable(next_hop_attr):
    
                def logging_attr(*args, **kwargs):
                    retval = next_hop_attr(*args, **kwargs)
                    print("logging a call to {0} with args -{1}-".format(name, ','.join(args)))
                    print("Returned: {0}".format(retval))
                    return retval
    
                return logging_attr
    
            else:
                return next_hop_attr
    
        def __setattr__(self, name, value):
            global _wrapped_object
            _wrapped_object.__setattr__(name, value)
    
    
    from win32com.client import Dispatch
    ie = Dispatch('InternetExplorer.Application')
    ie = LogWrap(ie)
    ie.Visible = True
    
    ie.Navigate2('google.com')
    

    With example output:

    >>> logging a call to Navigate2 with args -google.com-
    Returned: None
    >>>