Search code examples
pythonshutiltry-exceptonerror

Can a rmtree command with an onerror function be used within a try/except statement?


I've been trying to find an answer pertaining to how error handling and propagation are handled when using the shutil.rmtree command inside a try/except statement. I've tried to find an example showing such a practice, but have not been able to find one. So, I'm left wondering if it's even possible. When reading the documentation for the command here I see that it states:

...to remove a directory tree on Windows where some of the files have their read-only bit set. It uses the onerror callback to clear the readonly bit and reattempt the remove. Any subsequent failure will propagate.

Doesn't this mean that if an error occurs within the onerror function after the initial try to execute the onerror function, in other words the onerror function doesn't fix the error and it occurs when the shutil.rmtree tries to run again, that the error will be raised in the main routine (where the try/except statement is)? Is that what it means by "Any subsequent failure will propagate"?

I'm trying to make sure that if for whatever reason any of the shutil.rmtree commands fail that the failure will be caught and the code will still continue. I have another script that run after this script to check/correct errors in batches, which is why I'm not directly handling the errors. I just need to make sure this script runs all the way through. Will the code below accomplish this as it's written or is there anything that I need to change to accomplish this?

import shutil
import os
import stat

def remove_readonly(func, path, excinfo):
    os.chmod(path, stat.S_IWRITE)
    func(path)

try:
    #os.chmod is used to turn off Read-Only attribute
    os.chmod("Q:/-----.vbs", stat.S_IWRITE)
    #os.remove is used to remove individual files
    os.remove("Q:/-----.vbs")
except:
    pass

#shutil.rmtree is used to remove entire directories
#remove traces of file
try:
    shutil.rmtree("Q:/FolderToRemove1", onerror=remove_readonly)
except:
    pass

try:
    shutil.rmtree("Q:/FolderToRemove2", onerror=remove_readonly)
except:
    pass

try:
    shutil.rmtree("Q:/FolderToRemove3", onerror=remove_readonly)
except:
    pass

try:
    shutil.rmtree("Q:/FolderToRemove4", onerror=remove_readonly)
except:
    pass

try:
    shutil.rmtree("Q:/FolderToRemove5", onerror=remove_readonly)
except:
    pass

try:
    shutil.rmtree("C:/Users/mhill/Desktop/screenshots", onerror=remove_readonly)
except:
    pass

Solution

  • The documentation states that exceptions raised by onerror will not be caught, thus you must handle it yourself.

    In terms of your sample code, a blank except is generally poor design. In particular, it will also catch a KeyboardInterrupt which is surely not your intent.

    Rather, something like this:

    for f in ["Q:/FolderToRemove1", "Q:/FolderToRemove2", 
              "Q:/FolderToRemove3", "Q:/FolderToRemove4", 
              "Q:/FolderToRemove5", "C:/Users/mhill/Desktop/screenshots"]:
        try:
            shutil.rmtree(f, onerror=remove_readonly)
        except Exception:
            pass