exception handling with different error codes - python

I'd like to know how to handle different exceptions that have the same "type". I'm trying to use some code to make a directory:
os.mkdir(target_dir_name)
And I know this can fail for a variety of reasons, for example if the directory already exists:
OSError: [Errno 17] File exists:
or if there's no permissions to create a new dir:
OSError: [Errno 13] Permission denied:
I'd like to tailor my error messages to the specific failure cause, so I came up with the following code:
try:
os.mkdir(target_dir_name)
except OSError as e:
if e.errno == 17:
print "Warning: Directory %s already exists, executing a rebuild" % (target_dir_name)
elif e.errno == 13:
sys.exit("Error: Directory "+target_dir_name+" cannot be created incorrect permissions")
but I'd like to do something a little less hardcoded. Is there a Pythonic way I could update my sub-failure checks?

The only information I've found... which I guess is my best answer:
With the errno module I can at least rewrite my current code as:
import errno
#...
except OSError as e:
if e.errno == errno.EEXIST:
print "Warning: Directory %s already exists, e..."
elif e.errno == errno.EACCESS:
sys.exit("Error: Directory "+target_dir_name+"..."
If I upgrade to python 3.2 there is the much nicer:
except FileExistsError:
print ("Warning: Directory %s already exists, e...")
except PermissionError:
sys.exit("Error: Directory "+target_dir_name+"...")
Seems I could do something fancy with using sys.version_info.major and minor to make sure it's at least 3.2 as well.
Hopefully something better will come up...

Related

python Only handle OSError Errno 13 (permission denied)

I am migrating an application to python3 but there are legacy systems that cannot be upgraded yet using python2.
I have the application that needs to update a cache file but if for some reason the user launching the application cannot read/update the cache. it is no big deal and he can just query the db again instead of using the cache.
I would like therefore pass the permission denied exception on python2 it is an OSError [Errno 13].
on python3 i use PermissionError so that is okay. I assume that this exception will only catch the errno 13.
this is what i have on python3
for filename in os.listdir(cache_dir):
try:
if filename.endswith('.cache'):
os.remove(os.path.join(cache_dir, filename))
Except PermissionError:
pass
Except OSError:
#handle all other errors
how can I mimic the same on python2 so that only Errno 13 is passed and not anything else? eg I can pass if its a permission denied but not if the fs is read-only or the disk is full.
There is only OSError to catch, but you can check the error number contained in the exception.
import errno
for filename in os.listdir(cache_dir):
# Keep the try block as focused as possible.
if not filename.endswith('.cache'):
continue
fname = os.path.join(cache_dir, filename)
try:
os.remove(fname)
except OSError as e:
if e.errno != errno.EACCES:
# handle other errors
Use the errno module, because error numbers vary from one operating system to another.

Python Create Directory in Windows with a ':' in the Title

I think/hope this a an easy question that I just can't find the answer to - how do I create a directory where the title contains a colon? I'm scraping data from a website with variables for section and year, and creates a directory:
if 'C:/Path/Data/%s' % stat:
directory = 'C:/Path/Data/%s' % stat
else:
directory = 'C:/Path/Data/%s' % stat.replace('/', ' ')
if not os.path.exists(directory):
try:
os.makedirs(directory)
except OSError as e:
if not e.errno != errno.EEXIST:
raise
I have some error handling in there based on what's coming back. I had to bring in errno to skip the section that was erring. I want to bring in that data however - and I believe the issue is the title of the section, and thus the directory, contains a : in a directory like:
/Year: some_data/
I receive this error:
OSError: [Errno 22] Invalid argument:
Failed with OSError
My question is - how do I create or handle the creation of a directory with a : in the title? I'm just skipping over it for now.
I apologize if this is a duplicate but I couldn't find anything specifically. I'm running python 3.6 in Jupyter Notebook on Windows 10. Thank you.

How to solve FTP Error 2 Not a Directory

I am using the below code for downloading files from a ftp server.But I am getting an error [Errno 2] No such file or directory:, but the file present in the server and I can able download it via terminal. Can anyone help me!!
import ftplib
import os
remotpath='folder/subfolder'
try:
ftpclient = ftplib.FTP('ftp.xxxx.com')
ftpclient.login('user', 'pass')
ftpclient.cwd(remotpath)
print "login succeessfull"
files = ftpclient.nlst()
for eachFile in files:
saveTo = os.path.join(remotpath,eachFile)
if (not os.path.exists(saveTo)):
try:
ftpclient.retrbinary('RETR ' + eachFile, open(saveTo, 'wb').write)
#logging.info('\tdownloaded ' + saveTo)
downloaded += 1
except BaseException as e:
print('\terror downloading inside first %s - %s' % (eachFile, e.__str__()))
except ftplib.error_perm:
print('\terror downloading inside second %s - %s' % (eachFile, ftplib.error_perm))
except Exception as e:
print e
Does the destination directory ./folder/subfolder exist?
If not you need to create it before downloading files. Either do so using your OS commands (mkdir), or in Python using os.makedirs() :
import os
try:
os.makedirs(remotpath)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
You can add it somewhere before the for loop.
On another issue, the order of you exception handlers means that all exceptions raised in the inner try block would be handled in the except BaseException statement. This means that ftplib.error_perm will be caught in that statement because BaseException is more general, and not in the ftplib.error_perm statement as you might expect.
You should reorder your except statements in order of increasing generality.

shutil.rmtree doesn't work with Windows Library

So I'm building a simple script that backs up certain documents to my second hard-drive (you never know what could happen!). So, I used the shutil.copytree function to replicate my data on the second drive. It works beautifully, and that is not the problem.
I use the shutil.rmtree function to remove the tree if the destination already exists. I'll show you my code:
import shutil
import os
def overwrite(src, dest):
if(not os.path.exists(src)):
print(src, "does not exist, so nothing may be copied.")
return
if(os.path.exists(dest)):
shutil.rmtree(dest)
shutil.copytree(src, dest)
print(dest, "overwritten with data from", src)
print("")
overwrite(r"C:\Users\Centurion\Dropbox\Documents", r"D:\Backup\Dropbox Documents")
overwrite(r"C:\Users\Centurion\Pictures", r"D:\Backup\All Pictures")
print("Press ENTER to continue...")
input()
As you can see, a simple script. Now, when I run the script for the first time, everything is fine. Pictures and Documents copy over to my D: drive just fine. However, when I run for the second time, this is my output:
C:\Users\Centurion\Programming\Python>python cpdocsnpics.py
D:\Backup\Dropbox Documents overwritten with data from C:\Users\Centurion\Dropbox\Documents
Traceback (most recent call last):
File "cpdocsnpics.py", line 17, in <module>
overwrite(r"C:\Users\Centurion\Pictures", r"D:\Backup\All Pictures")
File "cpdocsnpics.py", line 10, in overwrite
shutil.rmtree(dest)
File "C:\Python34\lib\shutil.py", line 477, in rmtree
return _rmtree_unsafe(path, onerror)
File "C:\Python34\lib\shutil.py", line 376, in _rmtree_unsafe
onerror(os.rmdir, path, sys.exc_info())
File "C:\Python34\lib\shutil.py", line 374, in _rmtree_unsafe
os.rmdir(path)
PermissionError: [WinError 5] Access is denied: 'D:\\Backup\\All Pictures'
The error only happens when I copy Pictures after the first time; I'm assuming it has something to do with being a Library.
What should I do?
That's a cross-platform consistency issue.
You've copied files/dirs with readonly attribute. On the first time "dest" not exists, thus rmtree method is not performed. However, when you try run "overwrite" function we can notice that "dest" location exists (and its subtree) but it was copied with readonly access. So here we got a problem.
In order to "fix" issue, you must provide a handler for onerror parameter of shutil.rmtree. As long as your problem is regarding readonly issues the workaround is somewhat like this:
def readonly_handler(func, path, execinfo):
os.chmod(path, 128) #or os.chmod(path, stat.S_IWRITE) from "stat" module
func(path)
As you can see in the python doc onerror must be a callable that accepts three parameters: function, path, and excinfo. For further info, read the docs.
def overwrite(src, dest):
if(not os.path.exists(src)):
print(src, "does not exist, so nothing may be copied.")
return
if(os.path.exists(dest)):
shutil.rmtree(dest, onerror=readonly_handler)
shutil.copytree(src, dest)
print(dest, "overwritten with data from", src)
print("")
Of course, this handler is simple and specific but if other errors occur, new exceptions will be raised and this handler may not be able to fix them!
Note:
Tim Golden (Python for windows contributor) has been patching the shutil.rmtree issue and it seems it will be resolved in Python 3.5 (see issue 19643).
I found a problem other than the read-only file using shutil.rmtree on Windows (testing on Windows 7). I was using a combination of shutil.rmtree and shutil.copytree to create a test fixture in a test suite, so the sequence was being called repeatedly in a short period of tme (<1 sec intervals), and I was seeing unpredictable failures part way through the test suite, with both EACCES and ENOTEMPTY errors reported. The symptoms suggested to me that the shutil.rmtree function had not completed on return to the calling program, and that it was only after some time that the deleted filenames were available for re-use.
TL;DR: the solution isn't pretty - broadly, it renames the directory before deleting it, but there are a number of wrinkles that need to be handled because the Windows file system seems to take some time to catch up with the operations perfumed. The actual code catches a variety of failure conditions and retries a variant of the failed operation after a short delay.
A longer discussion follows, with my final code at the end.
My first thought was to try renaming the directory tree before removing it, so that the original directory name is immediately available for re-use. This does appear to help. To this end, I created a replacement for rmtree whose essence is this:
def removetree(tgt):
def error_handler(func, path, execinfo):
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist - treat as success
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path) # read-only file; make writable and retry
raise e
tmp = os.path.join(os.path.dirname(tgt),"_removetree_tmp")
os.rename(tgt, tmp)
shutil.rmtree(tmp, onerror=error_handler)
return
I found this logic was an improvement, but it was subject to unpredictable failure of the os.rename operation, with one of several possible errors. So I also added some retry logic around os.rename, thus:
def removetree(tgt):
def error_handler(func, path, execinfo):
# figure out recovery based on error...
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path) # read-only file; make writable and retry
raise e
# Rename target directory to temporary value, then remove it
count = 0
while count < 10: # prevents indefinite loop
count += 1
tmp = os.path.join(os.path.dirname(tgt),"_removetree_tmp_%d"%(count))
try:
os.rename(tgt, tmp)
shutil.rmtree(tmp, onerror=error_handler)
break
except OSError as e:
time.sleep(1) # Give file system some time to catch up
if e.errno in [errno.EACCES, errno.ENOTEMPTY]:
continue # Try another temp name
if e.errno == errno.EEXIST:
shutil.rmtree(tmp, ignore_errors=True) # Try to clean up old files
continue # Try another temp name
if e.errno == errno.ENOENT:
break # 'src' does not exist(?)
raise # Other error - propagate
return
The above code is not tested, but the general idea here does seem to work. The full code I actually use is below, and uses two functions. It probably contains some unnecessary logic, but does seem to be working more reliably for me (in that my test suite now passes repeatedly on Windows where previously it failed unpredictably on a majority of runs):
def renametree_temp(src):
"""
Rename tree to temporary name, and return that name, or
None if the source directory does not exist.
"""
count = 0
while count < 10: # prevents indefinite loop
count += 1
tmp = os.path.join(os.path.dirname(src),"_removetree_tmp_%d"%(count))
try:
os.rename(src, tmp)
return tmp # Success!
except OSError as e:
time.sleep(1)
if e.errno == errno.EACCES:
log.warning("util.renametree_temp: %s EACCES, retrying"%tmp)
continue # Try another temp name
if e.errno == errno.ENOTEMPTY:
log.warning("util.renametree_temp: %s ENOTEMPTY, retrying"%tmp)
continue # Try another temp name
if e.errno == errno.EEXIST:
log.warning("util.renametree_temp: %s EEXIST, retrying"%tmp)
shutil.rmtree(tmp, ignore_errors=True) # Try to clean up old files
continue # Try another temp name
if e.errno == errno.ENOENT:
log.warning("util.renametree_temp: %s ENOENT, skipping"%tmp)
break # 'src' does not exist(?)
raise # Other error: propagaee
return None
def removetree(tgt):
"""
Work-around for python problem with shutils tree remove functions on Windows.
See:
https://stackoverflow.com/questions/23924223/
https://stackoverflow.com/questions/1213706/
https://stackoverflow.com/questions/1889597/
http://bugs.python.org/issue19643
"""
# shutil.rmtree error handler that attempts recovery from attempts
# on Windows to remove a read-only file or directory (see links above).
def error_handler(func, path, execinfo):
e = execinfo[1]
if e.errno == errno.ENOENT or not os.path.exists(path):
return # path does not exist: nothing to do
if func in (os.rmdir, os.remove) and e.errno == errno.EACCES:
try:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
except Exception as che:
log.warning("util.removetree: chmod failed: %s"%che)
try:
func(path)
except Exception as rfe:
log.warning("util.removetree: 'func' retry failed: %s"%rfe)
if not os.path.exists(path):
return # Gone, assume all is well
raise
if e.errno == errno.ENOTEMPTY:
log.warning("util.removetree: Not empty: %s, %s"%(path, tgt))
time.sleep(1)
removetree(path) # Retry complete removal
return
log.warning("util.removetree: rmtree path: %s, error: %s"%(path, repr(execinfo)))
raise e
# Try renaming to a new directory first, so that the tgt is immediately
# available for re-use.
tmp = renametree_temp(tgt)
if tmp:
shutil.rmtree(tmp, onerror=error_handler)
return
(The above code incorporates a solution to the read-only file problem from What user do python scripts run as in windows?, which according to Deleting directory in Python is tested. I don't think I encounter the read-only file problem, so assume it is not tested in my test suite.)

What is a good solution to a bogus OSError, 13 (EACCES) using Python on Windows

Here is the code:
def make_dir(dir_name):
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
try:
os.makedirs(dir_name)
except OSError, e:
print "ErrorNo: %s (%s)" % (e.errno, errno.errorcode[e.errno])
raise
IFF the directory already exists, I get the following:
ErrorNo: 13 (EACCES)
Traceback (most recent call last):
File "run_pnoise.py", line 167, in <module>
make_dir("prep_dat")
File "run_pnoise.py", line 88, in make_dir
os.makedirs(dir_name)
File "c:\Program Files (x86)\Python27\lib\os.py", line 157, in makedirs
mkdir(name, mode)
WindowsError: [Error 5] Access is denied: 'prep_dat'
If I run the program again, it works, indicating that the program indeed does have access to the directory(s), since the shutil.rmtree call is obviously working. I have come up with a workaround which I will post. However, is there a better explanation and/or workaround?
My assumption is that the shutil.rmtree call is returning before the OS is totally done deleting all of the files and subdirectories. Also, since the shutil.rmtree call is not throwing an exception, any EACCESS (13) error on the makedirs call is likely bogus. My attempt (as modified after Apalala's comment):
def make_dir(dir_name):
retry = True
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
while retry:
try:
# per Apalala, sleeping before the makedirs() eliminates the exception!
time.sleep(0.001)
os.makedirs(dir_name)
except OSError, e:
#time.sleep(0.001) # moved to before the makedirs() call
#print "ErrorNo: %s (%s)" % (e.errno, errno.errorcode[e.errno])
if e.errno != 13: # eaccess
raise
else:
retry = False
This seems to work reliably. There is the race condition problem mentioned in other posts, however that seems unlikely, and would probably result in a different exception.
I had the same problem, and this looks similar to my solution, except I was sleeping (0.1).
Can't you simply use an «except» statement ?
def make_dir(dir_name):
retry = True
if os.path.exists(dir_name):
shutil.rmtree(dir_name)
while retry:
try:
os.makedirs(dir_name)
except OSError, e:
time.sleep(0.001)
if e.errno != 13: # eaccess
raise
except WindowsError:
# (do some stuff)
else:
retry = False
It should work, no ?!
In your previous post, you said the program raised a «WindowsError» exception:
WindowsError: [Error 5] Access is denied: 'prep_dat'
Your code can handle «OSError» exceptions using the «except» statement, but it cannot handle «WindowsError» exceptions... if you want to handle «WindowsError» exceptions, you must use the «except» statement like this:
except WindowsError:
# (do some stuff)
Note that you can handle any exception like this:
except Exception, e:
# this code will catch all raised exceptions. The variable «e» contains an instance of the raised exception.

Categories