diff --git a/Lib/shutil.py b/Lib/shutil.py index 3c02776..d81b780 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -83,6 +83,12 @@ def copyfileobj(fsrc, fdst, length=16*1024): def _samefile(src, dst): # Macintosh, Unix. + if isinstance(src, os.DirEntry) and hasattr(os.path, 'samestat'): + try: + return os.path.samestat(src.stat(), os.stat(dst)) + except OSError: + return False + if hasattr(os.path, 'samefile'): try: return os.path.samefile(src, dst) @@ -93,6 +99,15 @@ def _samefile(src, dst): return (os.path.normcase(os.path.abspath(src)) == os.path.normcase(os.path.abspath(dst))) +def _stat(fn): + return fn.stat() if isinstance(fn, os.DirEntry) else os.stat(fn) + +def _islink(fn): + return fn.is_symlink() if isinstance(fn, os.DirEntry) else os.path.islink(fn) + +def _isdir(fn): + return fn.is_dir() if isinstance(fn, os.DirEntry) else os.path.isdir(fn) + def copyfile(src, dst, *, follow_symlinks=True): """Copy data from src to dst. @@ -105,16 +120,17 @@ def copyfile(src, dst, *, follow_symlinks=True): for fn in [src, dst]: try: - st = os.stat(fn) + st = _stat(fn) except OSError: # File most likely does not exist pass else: # XXX What about other special files? (sockets, devices...) if stat.S_ISFIFO(st.st_mode): + fn = fn.path if isinstance(fn, os.DirEntry) else fn raise SpecialFileError("`%s` is a named pipe" % fn) - if not follow_symlinks and os.path.islink(src): + if not follow_symlinks and _islink(src): os.symlink(os.readlink(src), dst) else: with open(src, 'rb') as fsrc: @@ -130,13 +146,13 @@ def copymode(src, dst, *, follow_symlinks=True): (e.g. Linux) this method does nothing. """ - if not follow_symlinks and os.path.islink(src) and os.path.islink(dst): + if not follow_symlinks and _islink(src) and os.path.islink(dst): if hasattr(os, 'lchmod'): stat_func, chmod_func = os.lstat, os.lchmod else: return elif hasattr(os, 'chmod'): - stat_func, chmod_func = os.stat, os.chmod + stat_func, chmod_func = _stat, os.chmod else: return @@ -181,7 +197,7 @@ def copystat(src, dst, *, follow_symlinks=True): pass # follow symlinks (aka don't not follow symlinks) - follow = follow_symlinks or not (os.path.islink(src) and os.path.islink(dst)) + follow = follow_symlinks or not (_islink(src) and os.path.islink(dst)) if follow: # use the real function if it exists def lookup(name): @@ -195,7 +211,11 @@ def copystat(src, dst, *, follow_symlinks=True): return fn return _nop - st = lookup("stat")(src, follow_symlinks=follow) + if follow and isinstance(src, os.DirEntry): + st = src.stat() + else: + st = lookup("stat")(src, follow_symlinks=follow) + mode = stat.S_IMODE(st.st_mode) lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), follow_symlinks=follow) @@ -306,43 +326,51 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, function that supports the same signature (like copy()) can be used. """ - names = os.listdir(src) + # TODO: use with statement; omitted just to make code review easier + # (will add later) + entries = os.scandir(src) if ignore is not None: + ls = [] + names = set() + for entry in entries: + ls.append(entry) + names.add(entry.name) ignored_names = ignore(src, names) + entries = ls else: ignored_names = set() os.makedirs(dst) errors = [] - for name in names: - if name in ignored_names: + for srcentry in entries: + if srcentry.name in ignored_names: continue - srcname = os.path.join(src, name) - dstname = os.path.join(dst, name) + srcname = os.path.join(src, srcentry.name) + dstname = os.path.join(dst, srcentry.name) try: - if os.path.islink(srcname): + if srcentry.is_symlink(): linkto = os.readlink(srcname) if symlinks: # We can't just leave it to `copy_function` because legacy # code with a custom `copy_function` may rely on copytree # doing the right thing. os.symlink(linkto, dstname) - copystat(srcname, dstname, follow_symlinks=not symlinks) + copystat(srcentry, dstname, follow_symlinks=not symlinks) else: # ignore dangling symlink if the flag is on if not os.path.exists(linkto) and ignore_dangling_symlinks: continue # otherwise let the copy occurs. copy2 will raise an error - if os.path.isdir(srcname): - copytree(srcname, dstname, symlinks, ignore, + if srcentry.is_dir(): + copytree(srcentry, dstname, symlinks, ignore, copy_function) else: - copy_function(srcname, dstname) - elif os.path.isdir(srcname): - copytree(srcname, dstname, symlinks, ignore, copy_function) + copy_function(srcentry, dstname) + elif srcentry.is_dir(): + copytree(srcentry, dstname, symlinks, ignore, copy_function) else: # Will raise a SpecialFileError for unsupported file types - copy_function(srcname, dstname) + copy_function(srcentry, dstname) # catch the Error from the recursive copytree so that we can # continue with other files except Error as err: