When copying files in Python, shutil.copy (and shutil.copy2) are able to silently overwrite the destination file if it already exists. At times this is the desired behavior, but I find that more often, I want to prevent overwriting the destination. A "naive" check would be this:
def supposedlySaferCopy(srcfile, destfile):
if not exists(destfile):
raise IOError('destination already exists')
shutil.copy(srcfile, destfile)
But it has a race condition: there is a small window of time between checking for existence and running the copy. Sometimes this is check is a safeguard, for example to make sure file operations in a complex script are not overwriting data when not expected to. In general this pattern can also be a security issue, e.g. a type of symlink race.
In Windows one can make a call directly to the Windows api; both CopyFile and MoveFile take a parameter for preventing overwrite. This can be done in pure Python because ctypes is built into Python's standard library (in 2.5 and later). In Posix systems, I wrote a copyFilePosixWithoutOverwrite function. The O_CREAT flag ensures the file is new, and the O_EXCL will hold the file handle exclusively. Here are my open and copy implementations:
def copy(srcfile, destfile, overwrite):
if not exists(srcfile):
raise IOError('source path does not exist')
if srcfile == destfile:
pass
elif sys.platform == 'win32':
from ctypes import windll, c_wchar_p, c_int
failIfExists = c_int(0) if overwrite else c_int(1)
res = windll.kernel32.CopyFileW(c_wchar_p(srcfile), c_wchar_p(destfile), failIfExists)
if not res:
raise IOError('CopyFileW failed')
else:
if overwrite:
shutil.copy(srcfile, destfile)
else:
copyFilePosixWithoutOverwrite(srcfile, destfile)
assertTrue(exists(destfile))
def move(srcfile, destfile, overwrite):
if not exists(srcfile):
raise IOError('source path does not exist')
if srcfile == destfile:
pass
elif sys.platform == 'win32':
from ctypes import windll, c_wchar_p, c_int
replaceExisting = c_int(1) if overwrite else c_int(0)
res = windll.kernel32.MoveFileExW(c_wchar_p(srcfile), c_wchar_p(destfile), replaceExisting)
if not res:
raise IOError('MoveFileExW failed')
else:
copy(srcfile, destfile, overwrite)
os.unlink(srcfile)
assertTrue(exists(destfile))
def copyFilePosixWithoutOverwrite(srcfile, destfile):
# fails if destination already exist. O_EXCL prevents other files from writing to location.
# raises OSError on failure.
flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
file_handle = os.open(destfile, flags)
with os.fdopen(file_handle, 'wb') as fdest:
with open(srcfile, 'rb') as fsrc:
while True:
buffer = fsrc.read(64 * 1024)
if not buffer:
break
fdest.write(buffer)
Fairly comprehensive tests and more file utilities can be found in tests.py and files.py on my GitHub page here.