14
14
from errno import *
15
15
from glob import _StringGlobber , _no_recurse_symlinks
16
16
from itertools import chain
17
- from stat import S_ISDIR , S_ISREG , S_ISSOCK , S_ISBLK , S_ISCHR , S_ISFIFO
17
+ from stat import (
18
+ S_IMODE , S_ISDIR , S_ISREG , S_ISLNK , S_ISSOCK , S_ISBLK , S_ISCHR , S_ISFIFO ,
19
+ )
18
20
from _collections_abc import Sequence
19
21
20
22
try :
27
29
grp = None
28
30
29
31
from pathlib ._os import (
30
- PathInfo , DirEntryInfo ,
31
32
vfsopen , vfspath ,
32
33
ensure_different_files , ensure_distinct_paths ,
33
- copyfile2 , copyfileobj , copy_info ,
34
+ copyfile2 , copyfileobj ,
34
35
)
35
36
36
37
@@ -612,6 +613,247 @@ class PureWindowsPath(PurePath):
612
613
__slots__ = ()
613
614
614
615
616
+ class _Info :
617
+ __slots__ = ('_path' ,)
618
+
619
+ def __init__ (self , path ):
620
+ self ._path = path
621
+
622
+ def __repr__ (self ):
623
+ path_type = "WindowsPath" if os .name == "nt" else "PosixPath"
624
+ return f"<{ path_type } .info>"
625
+
626
+ def _stat (self , * , follow_symlinks = True ):
627
+ """Return the status as an os.stat_result."""
628
+ raise NotImplementedError
629
+
630
+ def _posix_permissions (self , * , follow_symlinks = True ):
631
+ """Return the POSIX file permissions."""
632
+ return S_IMODE (self ._stat (follow_symlinks = follow_symlinks ).st_mode )
633
+
634
+ def _file_id (self , * , follow_symlinks = True ):
635
+ """Returns the identifier of the file."""
636
+ st = self ._stat (follow_symlinks = follow_symlinks )
637
+ return st .st_dev , st .st_ino
638
+
639
+ def _access_time_ns (self , * , follow_symlinks = True ):
640
+ """Return the access time in nanoseconds."""
641
+ return self ._stat (follow_symlinks = follow_symlinks ).st_atime_ns
642
+
643
+ def _mod_time_ns (self , * , follow_symlinks = True ):
644
+ """Return the modify time in nanoseconds."""
645
+ return self ._stat (follow_symlinks = follow_symlinks ).st_mtime_ns
646
+
647
+ if hasattr (os .stat_result , 'st_flags' ):
648
+ def _bsd_flags (self , * , follow_symlinks = True ):
649
+ """Return the flags."""
650
+ return self ._stat (follow_symlinks = follow_symlinks ).st_flags
651
+
652
+ if hasattr (os , 'listxattr' ):
653
+ def _xattrs (self , * , follow_symlinks = True ):
654
+ """Return the xattrs as a list of (attr, value) pairs, or an empty
655
+ list if extended attributes aren't supported."""
656
+ try :
657
+ return [
658
+ (attr , os .getxattr (self ._path , attr , follow_symlinks = follow_symlinks ))
659
+ for attr in os .listxattr (self ._path , follow_symlinks = follow_symlinks )]
660
+ except OSError as err :
661
+ if err .errno not in (EPERM , ENOTSUP , ENODATA , EINVAL , EACCES ):
662
+ raise
663
+ return []
664
+
665
+
666
+ _STAT_RESULT_ERROR = [] # falsy sentinel indicating stat() failed.
667
+
668
+
669
+ class _StatResultInfo (_Info ):
670
+ """Implementation of pathlib.types.PathInfo that provides status
671
+ information by querying a wrapped os.stat_result object. Don't try to
672
+ construct it yourself."""
673
+ __slots__ = ('_stat_result' , '_lstat_result' )
674
+
675
+ def __init__ (self , path ):
676
+ super ().__init__ (path )
677
+ self ._stat_result = None
678
+ self ._lstat_result = None
679
+
680
+ def _stat (self , * , follow_symlinks = True ):
681
+ """Return the status as an os.stat_result."""
682
+ if follow_symlinks :
683
+ if not self ._stat_result :
684
+ try :
685
+ self ._stat_result = os .stat (self ._path )
686
+ except (OSError , ValueError ):
687
+ self ._stat_result = _STAT_RESULT_ERROR
688
+ raise
689
+ return self ._stat_result
690
+ else :
691
+ if not self ._lstat_result :
692
+ try :
693
+ self ._lstat_result = os .lstat (self ._path )
694
+ except (OSError , ValueError ):
695
+ self ._lstat_result = _STAT_RESULT_ERROR
696
+ raise
697
+ return self ._lstat_result
698
+
699
+ def exists (self , * , follow_symlinks = True ):
700
+ """Whether this path exists."""
701
+ if follow_symlinks :
702
+ if self ._stat_result is _STAT_RESULT_ERROR :
703
+ return False
704
+ else :
705
+ if self ._lstat_result is _STAT_RESULT_ERROR :
706
+ return False
707
+ try :
708
+ self ._stat (follow_symlinks = follow_symlinks )
709
+ except (OSError , ValueError ):
710
+ return False
711
+ return True
712
+
713
+ def is_dir (self , * , follow_symlinks = True ):
714
+ """Whether this path is a directory."""
715
+ if follow_symlinks :
716
+ if self ._stat_result is _STAT_RESULT_ERROR :
717
+ return False
718
+ else :
719
+ if self ._lstat_result is _STAT_RESULT_ERROR :
720
+ return False
721
+ try :
722
+ st = self ._stat (follow_symlinks = follow_symlinks )
723
+ except (OSError , ValueError ):
724
+ return False
725
+ return S_ISDIR (st .st_mode )
726
+
727
+ def is_file (self , * , follow_symlinks = True ):
728
+ """Whether this path is a regular file."""
729
+ if follow_symlinks :
730
+ if self ._stat_result is _STAT_RESULT_ERROR :
731
+ return False
732
+ else :
733
+ if self ._lstat_result is _STAT_RESULT_ERROR :
734
+ return False
735
+ try :
736
+ st = self ._stat (follow_symlinks = follow_symlinks )
737
+ except (OSError , ValueError ):
738
+ return False
739
+ return S_ISREG (st .st_mode )
740
+
741
+ def is_symlink (self ):
742
+ """Whether this path is a symbolic link."""
743
+ if self ._lstat_result is _STAT_RESULT_ERROR :
744
+ return False
745
+ try :
746
+ st = self ._stat (follow_symlinks = False )
747
+ except (OSError , ValueError ):
748
+ return False
749
+ return S_ISLNK (st .st_mode )
750
+
751
+
752
+ class _DirEntryInfo (_Info ):
753
+ """Implementation of pathlib.types.PathInfo that provides status
754
+ information by querying a wrapped os.DirEntry object. Don't try to
755
+ construct it yourself."""
756
+ __slots__ = ('_entry' ,)
757
+
758
+ def __init__ (self , entry ):
759
+ super ().__init__ (entry .path )
760
+ self ._entry = entry
761
+
762
+ def _stat (self , * , follow_symlinks = True ):
763
+ """Return the status as an os.stat_result."""
764
+ return self ._entry .stat (follow_symlinks = follow_symlinks )
765
+
766
+ def exists (self , * , follow_symlinks = True ):
767
+ """Whether this path exists."""
768
+ if not follow_symlinks :
769
+ return True
770
+ try :
771
+ self ._stat (follow_symlinks = follow_symlinks )
772
+ except OSError :
773
+ return False
774
+ return True
775
+
776
+ def is_dir (self , * , follow_symlinks = True ):
777
+ """Whether this path is a directory."""
778
+ try :
779
+ return self ._entry .is_dir (follow_symlinks = follow_symlinks )
780
+ except OSError :
781
+ return False
782
+
783
+ def is_file (self , * , follow_symlinks = True ):
784
+ """Whether this path is a regular file."""
785
+ try :
786
+ return self ._entry .is_file (follow_symlinks = follow_symlinks )
787
+ except OSError :
788
+ return False
789
+
790
+ def is_symlink (self ):
791
+ """Whether this path is a symbolic link."""
792
+ try :
793
+ return self ._entry .is_symlink ()
794
+ except OSError :
795
+ return False
796
+
797
+
798
+ def _copy_info (info , target , follow_symlinks = True ):
799
+ """Copy metadata from the given PathInfo to the given local path."""
800
+ copy_times_ns = (
801
+ hasattr (info , '_access_time_ns' ) and
802
+ hasattr (info , '_mod_time_ns' ) and
803
+ (follow_symlinks or os .utime in os .supports_follow_symlinks ))
804
+ if copy_times_ns :
805
+ t0 = info ._access_time_ns (follow_symlinks = follow_symlinks )
806
+ t1 = info ._mod_time_ns (follow_symlinks = follow_symlinks )
807
+ os .utime (target , ns = (t0 , t1 ), follow_symlinks = follow_symlinks )
808
+
809
+ # We must copy extended attributes before the file is (potentially)
810
+ # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
811
+ copy_xattrs = (
812
+ hasattr (info , '_xattrs' ) and
813
+ hasattr (os , 'setxattr' ) and
814
+ (follow_symlinks or os .setxattr in os .supports_follow_symlinks ))
815
+ if copy_xattrs :
816
+ xattrs = info ._xattrs (follow_symlinks = follow_symlinks )
817
+ for attr , value in xattrs :
818
+ try :
819
+ os .setxattr (target , attr , value , follow_symlinks = follow_symlinks )
820
+ except OSError as e :
821
+ if e .errno not in (EPERM , ENOTSUP , ENODATA , EINVAL , EACCES ):
822
+ raise
823
+
824
+ copy_posix_permissions = (
825
+ hasattr (info , '_posix_permissions' ) and
826
+ (follow_symlinks or os .chmod in os .supports_follow_symlinks ))
827
+ if copy_posix_permissions :
828
+ posix_permissions = info ._posix_permissions (follow_symlinks = follow_symlinks )
829
+ try :
830
+ os .chmod (target , posix_permissions , follow_symlinks = follow_symlinks )
831
+ except NotImplementedError :
832
+ # if we got a NotImplementedError, it's because
833
+ # * follow_symlinks=False,
834
+ # * lchown() is unavailable, and
835
+ # * either
836
+ # * fchownat() is unavailable or
837
+ # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
838
+ # (it returned ENOSUP.)
839
+ # therefore we're out of options--we simply cannot chown the
840
+ # symlink. give up, suppress the error.
841
+ # (which is what shutil always did in this circumstance.)
842
+ pass
843
+
844
+ copy_bsd_flags = (
845
+ hasattr (info , '_bsd_flags' ) and
846
+ hasattr (os , 'chflags' ) and
847
+ (follow_symlinks or os .chflags in os .supports_follow_symlinks ))
848
+ if copy_bsd_flags :
849
+ bsd_flags = info ._bsd_flags (follow_symlinks = follow_symlinks )
850
+ try :
851
+ os .chflags (target , bsd_flags , follow_symlinks = follow_symlinks )
852
+ except OSError as why :
853
+ if why .errno not in (EOPNOTSUPP , ENOTSUP ):
854
+ raise
855
+
856
+
615
857
class Path (PurePath ):
616
858
"""PurePath subclass that can make system calls.
617
859
@@ -637,7 +879,7 @@ def info(self):
637
879
try :
638
880
return self ._info
639
881
except AttributeError :
640
- self ._info = PathInfo ( self )
882
+ self ._info = _StatResultInfo ( str ( self ) )
641
883
return self ._info
642
884
643
885
def stat (self , * , follow_symlinks = True ):
@@ -817,7 +1059,7 @@ def _filter_trailing_slash(self, paths):
817
1059
def _from_dir_entry (self , dir_entry , path_str ):
818
1060
path = self .with_segments (path_str )
819
1061
path ._str = path_str
820
- path ._info = DirEntryInfo (dir_entry )
1062
+ path ._info = _DirEntryInfo (dir_entry )
821
1063
return path
822
1064
823
1065
def iterdir (self ):
@@ -1123,7 +1365,7 @@ def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):
1123
1365
self .joinpath (child .name )._copy_from (
1124
1366
child , follow_symlinks , preserve_metadata )
1125
1367
if preserve_metadata :
1126
- copy_info (source .info , self )
1368
+ _copy_info (source .info , self )
1127
1369
else :
1128
1370
self ._copy_from_file (source , preserve_metadata )
1129
1371
@@ -1133,7 +1375,7 @@ def _copy_from_file(self, source, preserve_metadata=False):
1133
1375
with open (self , 'wb' ) as target_f :
1134
1376
copyfileobj (source_f , target_f )
1135
1377
if preserve_metadata :
1136
- copy_info (source .info , self )
1378
+ _copy_info (source .info , self )
1137
1379
1138
1380
if copyfile2 :
1139
1381
# Use fast OS routine for local file copying where available.
@@ -1155,12 +1397,12 @@ def _copy_from_file(self, source, preserve_metadata=False):
1155
1397
def _copy_from_symlink (self , source , preserve_metadata = False ):
1156
1398
os .symlink (vfspath (source .readlink ()), self , source .info .is_dir ())
1157
1399
if preserve_metadata :
1158
- copy_info (source .info , self , follow_symlinks = False )
1400
+ _copy_info (source .info , self , follow_symlinks = False )
1159
1401
else :
1160
1402
def _copy_from_symlink (self , source , preserve_metadata = False ):
1161
1403
os .symlink (vfspath (source .readlink ()), self )
1162
1404
if preserve_metadata :
1163
- copy_info (source .info , self , follow_symlinks = False )
1405
+ _copy_info (source .info , self , follow_symlinks = False )
1164
1406
1165
1407
def move (self , target ):
1166
1408
"""
0 commit comments