1616import posixpath
1717from glob import _GlobberBase , _no_recurse_symlinks
1818from stat import S_ISDIR , S_ISLNK , S_ISREG , S_ISSOCK , S_ISBLK , S_ISCHR , S_ISFIFO
19- from ._os import UnsupportedOperation , copyfileobj
19+ from pathlib ._os import copyfileobj
20+
21+
22+ __all__ = ["UnsupportedOperation" ]
23+
24+
25+ class UnsupportedOperation (NotImplementedError ):
26+ """An exception that is raised when an unsupported operation is attempted.
27+ """
28+ pass
2029
2130
2231@functools .cache
@@ -761,6 +770,13 @@ def symlink_to(self, target, target_is_directory=False):
761770 """
762771 raise UnsupportedOperation (self ._unsupported_msg ('symlink_to()' ))
763772
773+ def _symlink_to_target_of (self , link ):
774+ """
775+ Make this path a symlink with the same target as the given link. This
776+ is used by copy().
777+ """
778+ self .symlink_to (link .readlink ())
779+
764780 def hardlink_to (self , target ):
765781 """
766782 Make this path a hard link pointing to the same file as *target*.
@@ -806,21 +822,12 @@ def _copy_metadata(self, target, *, follow_symlinks=True):
806822 metadata = self ._read_metadata (keys , follow_symlinks = follow_symlinks )
807823 target ._write_metadata (metadata , follow_symlinks = follow_symlinks )
808824
809- def copy (self , target , * , follow_symlinks = True , preserve_metadata = False ):
825+ def _copy_file (self , target ):
810826 """
811- Copy the contents of this file to the given target. If this file is a
812- symlink and follow_symlinks is false, a symlink will be created at the
813- target.
827+ Copy the contents of this file to the given target.
814828 """
815- if not isinstance (target , PathBase ):
816- target = self .with_segments (target )
817829 if self ._samefile_safe (target ):
818830 raise OSError (f"{ self !r} and { target !r} are the same file" )
819- if not follow_symlinks and self .is_symlink ():
820- target .symlink_to (self .readlink ())
821- if preserve_metadata :
822- self ._copy_metadata (target , follow_symlinks = False )
823- return
824831 with self .open ('rb' ) as source_f :
825832 try :
826833 with target .open ('wb' ) as target_f :
@@ -832,42 +839,39 @@ def copy(self, target, *, follow_symlinks=True, preserve_metadata=False):
832839 f'Directory does not exist: { target } ' ) from e
833840 else :
834841 raise
835- if preserve_metadata :
836- self ._copy_metadata (target )
837842
838- def copytree (self , target , * , follow_symlinks = True ,
839- preserve_metadata = False , dirs_exist_ok = False ,
840- ignore = None , on_error = None ):
843+ def copy (self , target , * , follow_symlinks = True , dirs_exist_ok = False ,
844+ preserve_metadata = False , ignore = None , on_error = None ):
841845 """
842- Recursively copy this directory tree to the given destination.
846+ Recursively copy this file or directory tree to the given destination.
843847 """
844848 if not isinstance (target , PathBase ):
845849 target = self .with_segments (target )
846- if on_error is None :
847- def on_error (err ):
848- raise err
849850 stack = [(self , target )]
850851 while stack :
851- source_dir , target_dir = stack .pop ()
852+ src , dst = stack .pop ()
852853 try :
853- sources = source_dir . iterdir ()
854- target_dir . mkdir ( exist_ok = dirs_exist_ok )
855- if preserve_metadata :
856- source_dir ._copy_metadata (target_dir )
857- for source in sources :
858- if ignore and ignore ( source ):
859- continue
860- try :
861- if source . is_dir ( follow_symlinks = follow_symlinks ):
862- stack .append ((source , target_dir .joinpath (source .name )))
863- else :
864- source . copy ( target_dir . joinpath ( source . name ),
865- follow_symlinks = follow_symlinks ,
866- preserve_metadata = preserve_metadata )
867- except OSError as err :
868- on_error ( err )
854+ if not follow_symlinks and src . is_symlink ():
855+ dst . _symlink_to_target_of ( src )
856+ if preserve_metadata :
857+ src ._copy_metadata (dst , follow_symlinks = False )
858+ elif src . is_dir () :
859+ children = src . iterdir ()
860+ dst . mkdir ( exist_ok = dirs_exist_ok )
861+ for child in children :
862+ if not ( ignore and ignore ( child ) ):
863+ stack .append ((child , dst .joinpath (child .name )))
864+ if preserve_metadata :
865+ src . _copy_metadata ( dst )
866+ else :
867+ src . _copy_file ( dst )
868+ if preserve_metadata :
869+ src . _copy_metadata ( dst )
869870 except OSError as err :
871+ if on_error is None :
872+ raise
870873 on_error (err )
874+ return target
871875
872876 def rename (self , target ):
873877 """
0 commit comments