0

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.

alazyworkaholic
  • 537
  • 2
  • 8
  • I managed to get a workable solution by building off of TessellatingHeckler's response. – alazyworkaholic Oct 25 '16 at 09:32
  • `class COM_Wrapper(object): def __init__(self, COM_Class_String): self.__dict__['objCOM'] = win32com.client.DispatchEx(COM_Class_String) def __getattr__(self, name): if hasattr(self, 'objCOM'): if hasattr(self.objCOM, name): return self.objCOM.__getattr__(name) else: raise AttributeError("not in class nor COM Object") else: return None` '__getattr__` allows my class to have its own methods. (Avoid name clashes!) '_FlagAsMethod` & `__AttrToID__` are other useful python COM methods – alazyworkaholic Oct 25 '16 at 09:42

1 Answers1

0

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
>>> 
TessellatingHeckler
  • 27,511
  • 4
  • 48
  • 87
  • Thanks, this worked and solved my problem! If there's a more 'pythonic' way to do this I'd love to see it, or avoid global variables. – alazyworkaholic Sep 18 '16 at 21:54