14
14
from urllib .parse import quote_from_bytes as urlquote_from_bytes
15
15
16
16
17
- if os .name == 'nt' :
18
- from nt import _getfinalpathname
19
- else :
20
- _getfinalpathname = None
21
-
22
-
23
17
__all__ = [
24
18
"PurePath" , "PurePosixPath" , "PureWindowsPath" ,
25
19
"Path" , "PosixPath" , "WindowsPath" ,
29
23
# Internals
30
24
#
31
25
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
+
32
30
# EBADF - guard against macOS `stat` throwing EBADF
33
31
_IGNORED_ERROS = (ENOENT , ENOTDIR , EBADF , ELOOP )
34
32
35
33
_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 )
40
37
41
38
def _ignore_error (exception ):
42
39
return (getattr (exception , 'errno' , None ) in _IGNORED_ERROS or
@@ -186,30 +183,6 @@ def casefold_parts(self, parts):
186
183
def compile_pattern (self , pattern ):
187
184
return re .compile (fnmatch .translate (pattern ), re .IGNORECASE ).fullmatch
188
185
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
-
213
186
def _split_extended_path (self , s , ext_prefix = ext_namespace_prefix ):
214
187
prefix = ''
215
188
if s .startswith (ext_prefix ):
@@ -220,10 +193,6 @@ def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
220
193
s = '\\ ' + s [3 :]
221
194
return prefix , s
222
195
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
-
227
196
def is_reserved (self , parts ):
228
197
# NOTE: the rules for reserved names seem somewhat complicated
229
198
# (e.g. r"..\NUL" is reserved but not r"foo\NUL").
@@ -281,54 +250,6 @@ def casefold_parts(self, parts):
281
250
def compile_pattern (self , pattern ):
282
251
return re .compile (fnmatch .translate (pattern )).fullmatch
283
252
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
-
332
253
def is_reserved (self , parts ):
333
254
return False
334
255
@@ -424,6 +345,8 @@ def group(self, path):
424
345
425
346
expanduser = staticmethod (os .path .expanduser )
426
347
348
+ realpath = staticmethod (os .path .realpath )
349
+
427
350
428
351
_normal_accessor = _NormalAccessor ()
429
352
@@ -1132,15 +1055,27 @@ def resolve(self, strict=False):
1132
1055
normalizing it (for example turning slashes into backslashes under
1133
1056
Windows).
1134
1057
"""
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
1144
1079
1145
1080
def stat (self , * , follow_symlinks = True ):
1146
1081
"""
0 commit comments