Search code examples
pythonshutil

How to return a value from onerror callback in shutil.rmtree


Working with rmtree from shutil module, I made some tests to delete some directories and their content, knowing that rmtree takes a callback function on onerror argument, I created a function to keep track of the errors raised, code as below:

def _error_log(func, path, excinfo):
    """
    Function that track and record the errors when removing files/dirs

    Args:
        func (function): The function that raised the error.
        path (string): Path to the file/dir that raised the error.
        excinfo (tuple): The exception information returned by sys.exc_info().
    """

    # Log dict -> key= file/dir fullpath: value= [error class, error message, function that raised the error] 
    log = {}
    # Set the file/dir full path as key with an empty list as value
    log.setdefault(path, [])

    # Append the error class and message raised from excinfo tuple
    log[path].append(excinfo[0].__name__)
    log[path].append(excinfo[1].strerror)

    # Append the function that raised the error
    log[path].append(func.__name__)

    return log

# Import
import shutil

# Path to the directory to remove
path_to_remove = r"C:\shutil_test\remove_me"

shutil.rmtree(path_to_remove, onerror=_error_log)

How can I return the log dict from _error_log function ? I thought about using a global variable, but it is kind of messy.


Solution

  • I believe the cleanest way is to create a class and store your log as an attribute of the instance. A minimal implementation using your original function:

    class RmtreeErrorLogger:
        def __init__(self):
            # Log dict -> key= file/dir fullpath: value= [error class, error message, function that raised the error] 
            self.log = {}
    
        def logger(self, func, path, excinfo):
            # Set the file/dir full path as key with an empty list as value
            self.log.setdefault(path, [])
        
            # Append the error class and message raised from excinfo tuple
            self.log[path].append(excinfo[0].__name__)
            self.log[path].append(excinfo[1].strerror)
        
            # Append the function that raised the error
            self.log[path].append(func.__name__)
    

    To use it:

    import shutil
    
    # Path to the directory to remove
    path_to_remove = r"C:\shutil_test\remove_me"
    logger = RmtreeErrorLogger()
    
    shutil.rmtree(path_to_remove, onerror=logger.logger)
    
    # For example, check if there is any error:
    if logger.log:
        print('Errors happened')
    

    You could use a callable class to avoid having to say logger.logger.