Python distutils copy_tree fails to update if there are symlinks

Viewed 691

I'm trying to using Python distutils.dir_util.copy_tree to copy the contents of one directory to another, and update the files in the destination if they have changed. I'm using the options:

copy_tree(src, dst, preserve_symlinks=1, update=1)

This works for the initial copy, but fails on all subsequent ones with:

FileExistsError: [Errno 17] File exists: 'file1' -> 'test2/file2'

Where file1 is a symlink to file2.

I don't need it to update the symlinks. It's fine if it just ignores them the second time around, or blindly overwrites them. But, as it is, it seems impossible to copy a directory containing symlinks in update mode.

Is there some way to do this using this tool? Is there a better tool to use, or will I need to create my own?

Platform details:

CentOS Linux 7.5 Anaconda Python 3.6.6

1 Answers

Here's my attempt: a shim for distutils.dir_util.copy_tree with monkeypatching os.symlink.

import contextlib
import os
import distutils.dir_util


@contextlib.contextmanager
def monkeypatch(object, name, patch):
    value_orig = getattr(object, name)
    setattr(object, name, patch)
    yield object
    setattr(object, name, value_orig)


def copy_tree(src, dst, **kwargs):
    stdlib_symlink = os.symlink

    def _symlink(src, dst, **kwargs):
        try:
            stdlib_symlink(src, dst, **kwargs)
        except FileExistsError as err:
            pass

    with monkeypatch(distutils.dir_util.os, 'symlink', _symlink):
        distutils.dir_util.copy_tree(src, dst, **kwargs)

This version of _symlink function will ignore any errors occurring on symlink attempts. You can write your own that will recreate the symlink, something like

def _symlink(src, dst, **kwargs):
    try:
        stdlib_symlink(src, dst, **kwargs)
    except FileExistsError as err:
        os.remove(dst)
        stdlib_symlink(src, dst)
Related