1414from urllib .parse import quote_from_bytes as urlquote_from_bytes
1515
1616
17- if os .name == 'nt' :
18- from nt import _getfinalpathname
19- else :
20- _getfinalpathname = None
21-
22-
2317__all__ = [
2418 "PurePath" , "PurePosixPath" , "PureWindowsPath" ,
2519 "Path" , "PosixPath" , "WindowsPath" ,
2923# Internals
3024#
3125
26+ _WINERROR_NOT_READY = 21 # drive exists but is not accessible
27+ _WINERROR_INVALID_NAME = 123 # fix for bpo-35306
28+ _WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself
29+
3230# EBADF - guard against macOS `stat` throwing EBADF
3331_IGNORED_ERROS = (ENOENT , ENOTDIR , EBADF , ELOOP )
3432
3533_IGNORED_WINERRORS = (
36- 21 , # ERROR_NOT_READY - drive exists but is not accessible
37- 123 , # ERROR_INVALID_NAME - fix for bpo-35306
38- 1921 , # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
39- )
34+ _WINERROR_NOT_READY ,
35+ _WINERROR_INVALID_NAME ,
36+ _WINERROR_CANT_RESOLVE_FILENAME )
4037
4138def _ignore_error (exception ):
4239 return (getattr (exception , 'errno' , None ) in _IGNORED_ERROS or
@@ -186,30 +183,6 @@ def casefold_parts(self, parts):
186183 def compile_pattern (self , pattern ):
187184 return re .compile (fnmatch .translate (pattern ), re .IGNORECASE ).fullmatch
188185
189- def resolve (self , path , strict = False ):
190- s = str (path )
191- if not s :
192- return path ._accessor .getcwd ()
193- previous_s = None
194- if _getfinalpathname is not None :
195- if strict :
196- return self ._ext_to_normal (_getfinalpathname (s ))
197- else :
198- tail_parts = [] # End of the path after the first one not found
199- while True :
200- try :
201- s = self ._ext_to_normal (_getfinalpathname (s ))
202- except FileNotFoundError :
203- previous_s = s
204- s , tail = os .path .split (s )
205- tail_parts .append (tail )
206- if previous_s == s :
207- return path
208- else :
209- return os .path .join (s , * reversed (tail_parts ))
210- # Means fallback on absolute
211- return None
212-
213186 def _split_extended_path (self , s , ext_prefix = ext_namespace_prefix ):
214187 prefix = ''
215188 if s .startswith (ext_prefix ):
@@ -220,10 +193,6 @@ def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
220193 s = '\\ ' + s [3 :]
221194 return prefix , s
222195
223- def _ext_to_normal (self , s ):
224- # Turn back an extended path into a normal DOS-like path
225- return self ._split_extended_path (s )[1 ]
226-
227196 def is_reserved (self , parts ):
228197 # NOTE: the rules for reserved names seem somewhat complicated
229198 # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
@@ -281,54 +250,6 @@ def casefold_parts(self, parts):
281250 def compile_pattern (self , pattern ):
282251 return re .compile (fnmatch .translate (pattern )).fullmatch
283252
284- def resolve (self , path , strict = False ):
285- sep = self .sep
286- accessor = path ._accessor
287- seen = {}
288- def _resolve (path , rest ):
289- if rest .startswith (sep ):
290- path = ''
291-
292- for name in rest .split (sep ):
293- if not name or name == '.' :
294- # current dir
295- continue
296- if name == '..' :
297- # parent dir
298- path , _ , _ = path .rpartition (sep )
299- continue
300- if path .endswith (sep ):
301- newpath = path + name
302- else :
303- newpath = path + sep + name
304- if newpath in seen :
305- # Already seen this path
306- path = seen [newpath ]
307- if path is not None :
308- # use cached value
309- continue
310- # The symlink is not resolved, so we must have a symlink loop.
311- raise RuntimeError ("Symlink loop from %r" % newpath )
312- # Resolve the symbolic link
313- try :
314- target = accessor .readlink (newpath )
315- except OSError as e :
316- if e .errno != EINVAL and strict :
317- raise
318- # Not a symlink, or non-strict mode. We just leave the path
319- # untouched.
320- path = newpath
321- else :
322- seen [newpath ] = None # not resolved symlink
323- path = _resolve (path , target )
324- seen [newpath ] = path # resolved symlink
325-
326- return path
327- # NOTE: according to POSIX, getcwd() cannot contain path components
328- # which are symlinks.
329- base = '' if path .is_absolute () else accessor .getcwd ()
330- return _resolve (base , str (path )) or sep
331-
332253 def is_reserved (self , parts ):
333254 return False
334255
@@ -424,6 +345,8 @@ def group(self, path):
424345
425346 expanduser = staticmethod (os .path .expanduser )
426347
348+ realpath = staticmethod (os .path .realpath )
349+
427350
428351_normal_accessor = _NormalAccessor ()
429352
@@ -1132,15 +1055,27 @@ def resolve(self, strict=False):
11321055 normalizing it (for example turning slashes into backslashes under
11331056 Windows).
11341057 """
1135- s = self ._flavour .resolve (self , strict = strict )
1136- if s is None :
1137- # No symlink resolution => for consistency, raise an error if
1138- # the path doesn't exist or is forbidden
1139- self .stat ()
1140- s = str (self .absolute ())
1141- # Now we have no symlinks in the path, it's safe to normalize it.
1142- normed = self ._flavour .pathmod .normpath (s )
1143- return self ._from_parts ((normed ,))
1058+
1059+ def check_eloop (e ):
1060+ winerror = getattr (e , 'winerror' , 0 )
1061+ if e .errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME :
1062+ raise RuntimeError ("Symlink loop from %r" % e .filename )
1063+
1064+ try :
1065+ s = self ._accessor .realpath (self , strict = strict )
1066+ except OSError as e :
1067+ check_eloop (e )
1068+ raise
1069+ p = self ._from_parts ((s ,))
1070+
1071+ # In non-strict mode, realpath() doesn't raise on symlink loops.
1072+ # Ensure we get an exception by calling stat()
1073+ if not strict :
1074+ try :
1075+ p .stat ()
1076+ except OSError as e :
1077+ check_eloop (e )
1078+ return p
11441079
11451080 def stat (self , * , follow_symlinks = True ):
11461081 """
0 commit comments