Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Drop follow_symlinks argument for now.
  • Loading branch information
barneygale committed Jun 6, 2024
commit 0e1e4f12da3902166c5d59af9ff8f30b57c83439
6 changes: 1 addition & 5 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1477,15 +1477,11 @@ example because the path doesn't exist).
available. In previous versions, :exc:`NotImplementedError` was raised.


.. method:: Path.copy(target, *, follow_symlinks=True)
.. method:: Path.copy(target)

Copy the contents of this file to the *target* file. If *target* specifies
a file that already exists, it will be replaced.

If *follow_symlinks* is false, and this file is a symbolic link, *target*
will be created as a symbolic link. If *follow_symlinks* is true and this
file is a symbolic link, *target* will be a copy of the symlink target.

.. note::
This method uses operating system functionality to copy file content
efficiently. The OS might also copy some metadata, such as file
Expand Down
9 changes: 2 additions & 7 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,19 +793,14 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
raise UnsupportedOperation(self._unsupported_msg('mkdir()'))

def copy(self, target, follow_symlinks=True):
def copy(self, target):
"""
Copy the contents of this file to the given target. If this file is a
symlink and follow_symlinks is false, a symlink will be created at the
target.
Copy the contents of this file to the given target.
"""
if not isinstance(target, PathBase):
target = self.with_segments(target)
if self._samefile_safe(target):
raise OSError(f"{self!r} and {target!r} are the same file")
if not follow_symlinks and self.is_symlink():
target.symlink_to(self.readlink())
return
with self.open('rb') as source_f:
try:
with target.open('wb') as target_f:
Expand Down
10 changes: 4 additions & 6 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,21 +782,19 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
raise

if copyfile:
def copy(self, target, follow_symlinks=True):
def copy(self, target):
"""
Copy the contents of this file to the given target. If this file is a
symlink and follow_symlinks is false, a symlink will be created at the
target.
Copy the contents of this file to the given target.
"""
try:
target = os.fspath(target)
except TypeError:
if isinstance(target, PathBase):
# Target is an instance of PathBase but not os.PathLike.
# Use generic implementation from PathBase.
return PathBase.copy(self, target, follow_symlinks=follow_symlinks)
return PathBase.copy(self, target)
raise
copyfile(os.fspath(self), target, follow_symlinks)
copyfile(os.fspath(self), target)

def chmod(self, mode, *, follow_symlinks=True):
"""
Expand Down
25 changes: 2 additions & 23 deletions Lib/pathlib/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from errno import EBADF, EOPNOTSUPP, ETXTBSY, EXDEV
import os
import stat
import sys
try:
import fcntl
Expand Down Expand Up @@ -93,31 +92,11 @@ def copyfd(source_fd, target_fd):


if _winapi and hasattr(_winapi, 'CopyFile2'):
def is_dirlink(path):
try:
st = os.lstat(path)
except (OSError, ValueError):
return False
return (st.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY and
st.st_reparse_tag == stat.IO_REPARSE_TAG_SYMLINK)

def copyfile(source, target, follow_symlinks):
def copyfile(source, target):
"""
Copy from one file to another using CopyFile2 (Windows only).
"""
if follow_symlinks:
flags = 0
else:
flags = _winapi.COPY_FILE_COPY_SYMLINK
try:
_winapi.CopyFile2(source, target, flags)
return
except OSError as err:
# Check for ERROR_ACCESS_DENIED
if err.winerror != 5 or not is_dirlink(source):
raise
flags |= _winapi.COPY_FILE_DIRECTORY
_winapi.CopyFile2(source, target, flags)
_winapi.CopyFile2(source, target, 0)
else:
copyfile = None

Expand Down
22 changes: 1 addition & 21 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,7 @@ def test_copy_directory(self):
source.copy(target)

@needs_symlinks
def test_copy_symlink_follow_symlinks_true(self):
def test_copy_symlink(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
Expand All @@ -1721,26 +1721,6 @@ def test_copy_symlink_follow_symlinks_true(self):
self.assertFalse(target.is_symlink())
self.assertEqual(source.read_text(), target.read_text())

@needs_symlinks
def test_copy_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())

@needs_symlinks
def test_copy_directory_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkB'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())

def test_copy_to_existing_file(self):
base = self.cls(self.base)
source = base / 'fileA'
Expand Down
5 changes: 0 additions & 5 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3166,11 +3166,6 @@ static int winapi_exec(PyObject *m)
#define COPY_FILE_REQUEST_COMPRESSED_TRAFFIC 0x10000000
#endif
WINAPI_CONSTANT(F_DWORD, COPY_FILE_REQUEST_COMPRESSED_TRAFFIC);
#ifndef COPY_FILE_DIRECTORY
// Only defined in newer WinSDKs
#define COPY_FILE_DIRECTORY 0x00000080
#endif
WINAPI_CONSTANT(F_DWORD, COPY_FILE_DIRECTORY);

WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_STARTED);
WINAPI_CONSTANT(F_DWORD, COPYFILE2_CALLBACK_CHUNK_FINISHED);
Expand Down