Decorators for assisting in debugging Python code

      No Comments on Decorators for assisting in debugging Python code

WARNING: The following decorators are NOT production grade, not thoroughly tested and should only be used for debugging. I am aware that as far as decorators, they are not perfect and meant as a quick aid for development process. To write proper decorators, you should refer to this blog.

The code for this post can also be found in this Gist.

Helper: for_all_methods

Taken from this StackOverflow post (please read the comments on that answer!), slightly modified to exclude “private” methods (ones starting with _), the following decorator can be used on a class to decorate automatically all the non-private methods (refer to that post for usage):

def for_all_methods(decorator):
    """
    CLASS DECORATOR.
    When applied to a class, will automatically decorate all public 
    methods (ones that don't start with an underscore) using the 
    given ``decorator``.

    IMPORTANT NOTE: this hasn't been tested to be production grade.
    Please use with caution and for debugging only.

    Args:
        decorator: a decorator to use for decorating all class methods.

    Returns:
        A class (not an instance) where all its public methods are 
        decorated with given ``decorator``.
    """
    def decorate(cls):
        for attr in cls.__dict__:  # there's probably a better way to do this
            if not attr.startswith('_') and callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls

    return decorate

verify_thread_safety

The following decorator will record the first time a public method is being invoked for a specific object. Subsequent calls to any public method of that class will trigger thread-id verification, raising an exception in case the current thread is different than the first one.

The goal is somehow verify that an object is being used directly by a single thread through its life-cycle. It’s definitely a robust way, and being used by multiple thread doesn’t necessarily mean it’s not thread-safe. But it could give a hint what is going on and help you identify potential issues.

import wrapt
import functools

def verify_thread_safety(wrapped):
    """
    METHOD DECORATOR.
    When applied to a method, will record the current thread calling
    the method and verifies that previous calls also originated from 
    the same thread.
    When used on more than one method in a class, it will used the 
    same property to verify that all the decorated methods are being 
    accessed from a single thread.

    Recommended to use in conjunction with ``for_all_methods`` 
    decorator, which will automatically apply for all public methods 
    in a class.

    Args:
        wrapped: the method to wrap. It's expected to decorate a 
                 method (of a class), rather than a free function

    Returns:
        a decorated method.
    """
    @functools.wraps(wrapped)
    def decorate(self, *args, **kwargs):
        curr_thread = threading.current_thread()
        if not hasattr(self, 'called_from_tid'):
            self.called_from_tid = curr_thread.ident
            self.called_from_name = curr_thread.name
        assert curr_thread.ident == self.called_from_tid, "Method name is '{}'. First called from {}[{}]. Curr thread is {}[{}]".format(
            wrapped.__name__, self.called_from_name, self.called_from_tid, curr_thread.name, curr_thread.ident)
        return wrapped(self, *args, **kwargs)

    return wrapt.synchronized(decorate)

check_public_method_calls

The following decorator will “monitor” recursive calls to the public API of a specific object. In other words, it will allow the public API to be called only from outside the object (it will also block calls that originated from another public call). Where obviously it is plausible for an object to call to its public API, it could sometimes be indicative of an issue.

import wrapt
import functools

def check_public_method_calls(wrapped):
    """
    METHOD DECORATOR.
    Meant to be used in conjunction with ``for_all_methods`` 
    decorator, so it will be applied to all the public methods of the class.

    When applied, will verify that public methods are not being called
    down-the-stack from another public method.
    The decorator will impose synchronized access to all public 
    methods of the class.

    Args:
        wrapped: the method to wrap. It's expected to decorate a 
                 method (of a class), rather than a free function

    Returns:
        a decorated method.
    """
    @functools.wraps(wrapped)
    def decorate(self, *args, **kwargs):
        with self._lock:
            if not hasattr(self, 'method_called'):
                self.method_called = False
            assert not self.method_called, "Method was called recursively: " + wrapped.__name__
            self.method_called = True
            try:
                return_value = wrapped(self, *args, **kwargs)
            finally:
                self.method_called = False
            return return_value

    return wrapt.synchronized(decorate)

Leave a Reply