HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

A "safe" copy function for Python

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
functionpythonforsafecopy

Problem

I have a Python script for doing "safe" file copying.

There are a couple of rules for this copy function:

  • Existing files should never be overwritten. So if I try to copy $file1 to $location, and there is already $file2 at $location, then $file2 will not be overwritten.



  • Exact filenames are not important. So if I try to copy a file to $location.txt, and because there’s already a different file there, it instead gets copied to $location-1.txt, that’s okay.



  • Copying should be idempotent. That is, if I try to copy a file to $location, and it's already there, nothing else happens. I'm not going to create more copies at $location-1, $location-2, $location-3, etc.



An example use case: I'm importing some camera photos into a folder, but some of the photos may be duplicates (and I don't want two copies) or have the same filename as another photo (and I want to keep both, but small changes to the name aren't important).

```
import filecmp
import os
import shutil

def increment_filename(filename, marker="-"):
"""Appends a counter to a filename, or increments an existing counter."""
basename, fileext = os.path.splitext(filename)

# If there isn't a counter already, then append one
if marker not in basename:
components = [basename, 1, fileext]

# If it looks like there might be a counter, then try to coerce it to an
# integer and increment it. If that fails, then just append a new counter.
else:
base, counter = basename.rsplit(marker, 1)
try:
new_counter = int(counter) + 1
components = [base, new_counter, fileext]
except ValueError:
components = [base, 1, fileext]

# Drop in the marker before the counter
components.insert(1, marker)

new_filename = "%s%s%d%s" % tuple(components)
return new_filename

def copyfile(src, dst):
"""Copies a file from path src to path dst.

If a file already exists at dst, it will not be overwritten, bu

Solution

As the documentation for shutil.copyfile(src, dst) says,


If dst already exists, it will be replaced.

You have attempted to work around that by checking if not os.path.exists(dst) first. However, that logic is vulnerable to a race condition if the destination file springs into existence just after the check.

A way to ensure that you do not overwrite an existing file is to use os.open() with the os.O_EXCL flag. You would need to write the read-write loop yourself to transfer the file contents, but that is not difficult.

Context

StackExchange Code Review Q#89985, answer score: 5

Revisions (0)

No revisions yet.