Wrapping all possible method calls of a class in a try/except block
Question: I'm trying to wrap all methods of an existing Class (not of my creation) into
a try/except suite. It could be any Class, but I'll use the
**pandas.DataFrame** class here as a practical example.

So if the invoked method succeeds, we simply move on. But if it should
generate an exception, it is appended to a list for later inspection/discovery
(although the below example just issues a print statement for simplicity).

_(Note that the kinds of data-related exceptions that can occur when a method
on the instance is invoked, isn't yet known; and that's the reason for this
exercise: discovery)._

This [post](http://stackoverflow.com/questions/11349183/how-to-wrap-every-
method-of-a-class) was quite helpful (particularly @martineau Python-3
answer), but I'm having trouble adapting it. Below, I expected the second call
to the (wrapped) _info()_ method to emit print output but, sadly, it doesn't.

    
    
    #!/usr/bin/env python3
    
    import functools, types, pandas
    
    def method_wrapper(method):
        @functools.wraps(method)
        def wrapper(*args, **kwargs): #Note: args[0] points to 'self'.
            try:
                print('Calling: {}.{}()... '.format(args[0].__class__.__name__, method.__name__))
                return method(*args, **kwargs)
            except Exception:
                print('Exception: %r' % sys.exc_info()) # Something trivial.
                #<Actual code would append that exception info to a list>.
        return wrapper
    
    
    class MetaClass(type):
        def __new__(mcs, class_name, base_classes, classDict):
            newClassDict = {}
            for attributeName, attribute in classDict.items():
                if type(attribute) == types.FunctionType: # Replace it with a wrapper (decorated) version.
                    attribute = method_wrapper(attribute)
                newClassDict[attributeName] = attribute
            return type.__new__(mcs, class_name, base_classes, newClassDict)
    
    class WrappedDataFrame2(MetaClass('WrappedDataFrame',(pandas.DataFrame, object,), {}), metaclass=type):
        pass
    
    print('Unwrapped pandas.DataFrame().info():')
    pandas.DataFrame().info()
    
    print('\n\nWrapped pandas.DataFrame().info():')
    WrappedDataFrame2().info()
    print()
    

This outputs:

    
    
    Unwrapped pandas.DataFrame().info():
    <class 'pandas.core.frame.DataFrame'>
    Index: 0 entries
    Empty DataFrame
    
    Wrapped pandas.DataFrame().info():   <-- Missing print statement after this line.
    <class '__main__.WrappedDataFrame2'>
    Index: 0 entries
    Empty WrappedDataFrame2
    

In summary,...

    
    
    >>> unwrapped_object.someMethod(...)
    # Should be mirrored by ...
    
    >>> wrapping_object.someMethod(...)
    # Including signature, docstring, etc. (i.e. all attributes); except that it
    # executes inside a try/except suite (so I can catch exceptions generically).
    


Answer: Your metaclass only applies your decorator to the methods defined in classes
that are instances of it. It doesn't decorate inherited methods, since they're
not in the `classDict`.

I'm not sure there's a good way to make it work. You could try iterating
through the MRO and wrapping all the inherited methods as well as your own,
but I suspect you'd get into trouble if there were multiple levels of
inheritance after you start using `MetaClass` (as each level will decorate the
already decorated methods of the previous class).

