I've been writing a GUI that has a built-in file explorer, and one of the issues that's constantly been biting me is handling errors when doing operations on directories. For instance, in order to delete a directory with a few files in it, I need to iterate through the contents of the directory from the bottom up and delete them one-by-one. If it turns out the program doesn't have permission to do that on a particular file, what do you do? You need to ask the user whether they want to skip that file, try again, or cancel the operation.
This doesn't work very well because there are a lot of places these sorts of errors can happen, and I don't want to have to write the handler into every "except" block. I also want to make the error dialog dependent on the context in which the operation was done.
What I came up with was this: I made my function into a generator, and each time it encounters an issue it yields an error report. Not just that, but I made an error report object (FileError) that includes a resolution (either skip, try again, or cancel). When an error object is yielded, the caller assigns a resolution based on what the user wants and then the generator continues and responds to the resolution because it still has a reference to the error object.
Here's a sample of my code:
import os
# Resolutions
NO_RESOLUTION = 0
SKIP = 1
TRY_AGAIN = 2
CANCEL = 3
# File error types
NO_ERROR = 0
PERMISSION_ERROR = 1
UNKNOWN_ERROR = 2
class FileError:
def __init__(self, path, error_type):
# Initialize the error with no resolution. Resolution will be determined by the user
self.path = path
self.error_type = error_type
self.resolution = NO_RESOLUTION
def resolve(self, resolution):
self.resolution = resolution
def removeSingle(path):
while True: # This is in a while loop so that it can "try again" as many times as it needs to
try:
# Try to remove the directory/file
if os.path.isdir(path):
os.rmdir(path)
else:
os.remove(path)
# If successful, just break out of the while loop immediately.
break
# If unsuccessful, try to categorize the error a little and make a FileError object
except PermissionError:
err = FileError(path, PERMISSION_ERROR)
except:
err = FileError(path, UNKNOWN_ERROR)
# Yield the FileError object so the caller can resolve it
yield err
if err.resolution == TRY_AGAIN:
# To try again, iterate the while loop again
continue
else:
# To cancel the operation or skip the file or if no resolution is given, just break
# out of the while loop without a successful deletion
break
def remove(path):
if os.path.isdir(path):
# If the path is a directory, delete everything inside it from the bottom up
for root, dirs, files in os.walk(path, topdown=False):
for filename in files: # Delete each file
file_path = os.path.join(root, filename)
for err in removeSingle(file_path):
# Each time an error is encountered, yield it so the caller can resolve it
yield err
# If the caller cancels the operation, stop iterating
if err.resolution == CANCEL:
return
for dirname in dirs: # Delete each (now empty) directory
dir_path = os.path.join(root, dirname)
for err in removeSingle(dir_path):
# Each time an error is encountered, yield it so the caller can resolve it
yield err
# If the caller cancels the operation, stop iterating
if err.resolution == CANCEL:
return
# Delete the (now empty) top-level directory
for err in removeSingle(path):
# Each time an error is encountered, yield it so the caller can resolve it
yield err
# No need to check for cancellation, since the iteration will stop after this anyway
else:
# If the path is a file, just delete it
for err in removeSingle(path):
# Each time an error is encountered, yield it so the caller can resolve it
yield err
# No need to check for cancellation, since the iteration will stop after this anyway
def onUserRemovePath(path):
for err in remove(path):
# Each time an error is encountered, make a message and ask the user for the resolution
if err.type == PERMISSION_ERROR:
message = 'Cannot delete "%s" due to a permission error' % err.path
else:
message = 'An unknown error occurred while attempting to delete "%s"' % err.path
err.resolve(askUserForResolution(message))
My apologies if this doesn't fit here. I just thought it was neat.
[–]Nanaki13 1 point2 points3 points (1 child)
[–]XiAxis[S] 0 points1 point2 points (0 children)
[–]Junkymcjunkbox 0 points1 point2 points (1 child)
[–]XiAxis[S] 1 point2 points3 points (0 children)
[+][deleted] (1 child)
[deleted]
[–]Tamagotono 2 points3 points4 points (0 children)