From c90a883dafbc88f5a4f1fd5a8870fa5aa21975a2 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Thu, 28 Mar 2024 19:22:08 +0100 Subject: [PATCH 01/46] Speed up `posixpath.ismount` --- Lib/posixpath.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 4fc02be69bd6e1..f4b6295dc847f4 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -197,31 +197,19 @@ def ismount(path): except (OSError, ValueError): # It doesn't exist -- so not a mount point. :-) return False - else: - # A symlink can never be a mount point - if stat.S_ISLNK(s1.st_mode): - return False - path = os.fspath(path) - if isinstance(path, bytes): - parent = join(path, b'..') - else: - parent = join(path, '..') - parent = realpath(parent) + # A symlink can never be a mount point + if stat.S_ISLNK(s1.st_mode): + return False + + parent = realpath(dirname(path)) try: s2 = os.lstat(parent) except (OSError, ValueError): return False - dev1 = s1.st_dev - dev2 = s2.st_dev - if dev1 != dev2: - return True # path/.. on a different device as path - ino1 = s1.st_ino - ino2 = s2.st_ino - if ino1 == ino2: - return True # path/.. is the same i-node as path - return False + # path/.. on a different device as path or the same i-node as path + return s1.st_dev != s2.st_dev or s1.st_ino == s2.st_ino # Expand paths beginning with '~' or '~user'. From 598495952c92b37afc3bedfc9c31a3ddd2425869 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Thu, 28 Mar 2024 19:23:30 +0100 Subject: [PATCH 02/46] Speed up `posixpath.expanduser` --- Lib/posixpath.py | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index f4b6295dc847f4..0dc0e52957fea2 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -235,37 +235,31 @@ def expanduser(path): i = path.find(sep, 1) if i < 0: i = len(path) - if i == 1: - if 'HOME' not in os.environ: - try: - import pwd - except ImportError: - # pwd module unavailable, return path unchanged - return path - try: - userhome = pwd.getpwuid(os.getuid()).pw_dir - except KeyError: - # bpo-10496: if the current user identifier doesn't exist in the - # password database, return the path unchanged - return path - else: - userhome = os.environ['HOME'] - else: + if i != 1: try: import pwd - except ImportError: + name = path[1:i] + if isinstance(name, bytes): + name = name.decode('ascii') + + userhome = pwd.getpwnam(name).pw_dir + except (ImportError, KeyError): # pwd module unavailable, return path unchanged + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged return path - name = path[1:i] - if isinstance(name, bytes): - name = str(name, 'ASCII') + elif 'HOME' in os.environ: + userhome = os.environ['HOME'] + else: try: - pwent = pwd.getpwnam(name) - except KeyError: - # bpo-10496: if the user name from the path doesn't exist in the + import pwd + userhome = pwd.getpwuid(os.getuid()).pw_dir + except (ImportError, KeyError): + # pwd module unavailable, return path unchanged + # bpo-10496: if the current user identifier doesn't exist in the # password database, return the path unchanged return path - userhome = pwent.pw_dir + # if no user home, return the path unchanged on VxWorks if userhome is None and sys.platform == "vxworks": return path From 833ddc9a8e925594dcb36620736f99192fc80a53 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Thu, 28 Mar 2024 19:25:06 +0100 Subject: [PATCH 03/46] Speed up `posixpath.normpath` --- Lib/posixpath.py | 55 ++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 0dc0e52957fea2..b6d8da860c3450 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -334,45 +334,40 @@ def expandvars(path): try: from posix import _path_normpath - + def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." + return _path_normpath(path) or "." except ImportError: def normpath(path): """Normalize path, eliminating double slashes, etc.""" path = os.fspath(path) if isinstance(path, bytes): sep = b'/' - empty = b'' - dot = b'.' - dotdot = b'..' + curdir = b'.' + pardir = b'..' else: sep = '/' - empty = '' - dot = '.' - dotdot = '..' - if path == empty: - return dot - _, initial_slashes, path = splitroot(path) - comps = path.split(sep) - new_comps = [] - for comp in comps: - if comp in (empty, dot): + curdir = '.' + pardir = '..' + if not path: + return curdir + _, root, tail = splitroot(path) + comps = [] + for comp in tail.split(sep): + if not comp or comp == curdir: continue - if (comp != dotdot or (not initial_slashes and not new_comps) or - (new_comps and new_comps[-1] == dotdot)): - new_comps.append(comp) - elif new_comps: - new_comps.pop() - comps = new_comps - path = initial_slashes + sep.join(comps) - return path or dot - -else: - def normpath(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." - return _path_normpath(path) or "." + if ( + comp != pardir + or (not root and not comps) + or (comps and comps[-1] == pardir) + ): + comps.append(comp) + elif comps: + comps.pop() + return (root + sep.join(comps)) or curdir def abspath(path): From a4d9fcb6404de71b551dd9e557accd0631bd5a8a Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Thu, 28 Mar 2024 22:06:45 +0100 Subject: [PATCH 04/46] Refactor `posixpath.expandvars` & `ntpath.commonpath` --- Lib/ntpath.py | 35 ++++++++++++++++------------------- Lib/posixpath.py | 5 ++--- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index f1c48ecd1e5e2a..ae5312feb506a8 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -858,33 +858,30 @@ def commonpath(paths): curdir = '.' try: - drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] - split_paths = [p.split(sep) for d, r, p in drivesplits] + rootsplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] - if len({r for d, r, p in drivesplits}) != 1: - raise ValueError("Can't mix absolute and relative paths") - - # Check that all drive letters or UNC paths match. The check is made only - # now otherwise type errors for mixing strings and bytes would not be - # caught. - if len({d for d, r, p in drivesplits}) != 1: + # Check that all drive letters or UNC paths match. The check is made + # only now otherwise type errors for mixing strings and bytes would not + # be caught. + if len({d for d, _, _ in rootsplits}) != 1: raise ValueError("Paths don't have the same drive") - drive, root, path = splitroot(paths[0].replace(altsep, sep)) - common = path.split(sep) - common = [c for c in common if c and c != curdir] + if len({r for _, r, _ in rootsplits}) != 1: + raise ValueError("Can't mix absolute and relative paths") + + drive, root, tail = splitroot(paths[0].replace(altsep, sep)) + common = [c for c in tail.split(sep) if c and c != curdir] - split_paths = [[c for c in s if c and c != curdir] for s in split_paths] + split_paths = [ + [c for c in t.split(sep) if c and c != curdir] + for _, _, t in rootsplits + ] s1 = min(split_paths) s2 = max(split_paths) for i, c in enumerate(s1): if c != s2[i]: - common = common[:i] - break - else: - common = common[:len(s1)] - - return drive + root + sep.join(common) + return drive + root + sep.join(common[:i]) + return drive + root + sep.join(common[:len(s1)]) except (TypeError, AttributeError): genericpath._check_arg_types('commonpath', *paths) raise diff --git a/Lib/posixpath.py b/Lib/posixpath.py index b6d8da860c3450..fba011881b9dd3 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -318,13 +318,12 @@ def expandvars(path): value = os.fsencode(os.environ[os.fsdecode(name)]) else: value = environ[name] - except KeyError: - i = j - else: tail = path[j:] path = path[:i] + value i = len(path) path += tail + except KeyError: + i = j return path From b853d4d450c2944262d9433c0aec8b254db64b46 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 10:56:56 +0100 Subject: [PATCH 05/46] Remove start- & endswith --- Lib/ntpath.py | 4 +- Lib/posixpath.py | 95 +++++++++++++++++++++++++----------------------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index ae5312feb506a8..f724f7beff0177 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -340,7 +340,7 @@ def isreserved(path): def _isreservedname(name): """Return true if the filename is reserved by the system.""" # Trailing dots and spaces are reserved. - if name.endswith(('.', ' ')) and name not in ('.', '..'): + if name[-1:] in ('.', ' ') and name not in ('.', '..'): return True # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved. # ASCII control characters (0-31) are reserved. @@ -373,7 +373,7 @@ def expanduser(path): tilde = b'~' else: tilde = '~' - if not path.startswith(tilde): + if path[:1] != tilde: return path i, n = 1, len(path) while i < n and path[i] not in _get_bothseps(path): diff --git a/Lib/posixpath.py b/Lib/posixpath.py index fba011881b9dd3..713a85fbc2606d 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -61,7 +61,7 @@ def isabs(s): """Test whether a path is absolute""" s = os.fspath(s) sep = _get_sep(s) - return s.startswith(sep) + return s[:1] == sep # Join pathnames. @@ -80,9 +80,9 @@ def join(a, *p): if not p: path[:0] + sep #23780: Ensure compatible data type even if p is null. for b in map(os.fspath, p): - if b.startswith(sep): + if b[:1] == sep: path = b - elif not path or path.endswith(sep): + elif not path or path[-1:] == sep: path += b else: path += sep + b @@ -229,7 +229,7 @@ def expanduser(path): tilde = b'~' else: tilde = '~' - if not path.startswith(tilde): + if path[:1] != tilde: return path sep = _get_sep(path) i = path.find(sep, 1) @@ -311,7 +311,7 @@ def expandvars(path): break i, j = m.span(0) name = m.group(1) - if name.startswith(start) and name.endswith(end): + if name[:1] == start and name[-1:] == end: name = name[1:-1] try: if environ is None: @@ -331,6 +331,34 @@ def expandvars(path): # It should be understood that this may change the meaning of the path # if it contains symbolic links! +def _normpath_fallback(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'/' + curdir = b'.' + pardir = b'..' + else: + sep = '/' + curdir = '.' + pardir = '..' + if not path: + return curdir + _, root, tail = splitroot(path) + comps = [] + for comp in tail.split(sep): + if not comp or comp == curdir: + continue + if ( + comp != pardir + or (not root and not comps) + or (comps and comps[-1] == pardir) + ): + comps.append(comp) + elif comps: + comps.pop() + return (root + sep.join(comps)) or curdir + try: from posix import _path_normpath def normpath(path): @@ -340,33 +368,7 @@ def normpath(path): return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." return _path_normpath(path) or "." except ImportError: - def normpath(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - sep = b'/' - curdir = b'.' - pardir = b'..' - else: - sep = '/' - curdir = '.' - pardir = '..' - if not path: - return curdir - _, root, tail = splitroot(path) - comps = [] - for comp in tail.split(sep): - if not comp or comp == curdir: - continue - if ( - comp != pardir - or (not root and not comps) - or (comps and comps[-1] == pardir) - ): - comps.append(comp) - elif comps: - comps.pop() - return (root + sep.join(comps)) or curdir + normpath = _normpath_fallback def abspath(path): @@ -388,11 +390,11 @@ def realpath(filename, *, strict=False): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" filename = os.fspath(filename) - path, ok = _joinrealpath(filename[:0], filename, strict, {}) + path, _ = _joinrealpath(filename[:0], filename, strict, {}) return abspath(path) -# Join two paths, normalizing and eliminating any symbolic links -# encountered in the second path. +# Join two paths, normalizing and eliminating any symbolic links encountered in +# the second path. Two leading slashes are replaced by a single slash. def _joinrealpath(path, rest, strict, seen): if isinstance(path, bytes): sep = b'/' @@ -414,22 +416,24 @@ def _joinrealpath(path, rest, strict, seen): continue if name == pardir: # parent dir - if path: - path, name = split(path) - if name == pardir: - path = join(path, pardir, pardir) - else: + if not path: + # .. path = pardir + elif basename(path) == pardir: + # ../.. + path = join(path, pardir) + else: + # foo/bar/.. -> foo + path = dirname(path) continue newpath = join(path, name) try: st = os.lstat(newpath) + is_link = stat.S_ISLNK(st.st_mode) except OSError: if strict: raise is_link = False - else: - is_link = stat.S_ISLNK(st.st_mode) if not is_link: path = newpath continue @@ -441,12 +445,11 @@ def _joinrealpath(path, rest, strict, seen): # use cached value continue # The symlink is not resolved, so we must have a symlink loop. - if strict: - # Raise OSError(errno.ELOOP) - os.stat(newpath) - else: + if not strict: # Return already resolved part + rest of the path unchanged. return join(newpath, rest), False + # Raise OSError(errno.ELOOP) + os.stat(newpath) seen[newpath] = None # not resolved symlink path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen) if not ok: From a8984dc8211942d57b5aa7733510d3d817daf24f Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 11:08:18 +0100 Subject: [PATCH 06/46] Remove `startswith` --- Lib/ntpath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index f724f7beff0177..83f55b362b3f61 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -737,7 +737,7 @@ def realpath(path, *, strict=False): # bpo-38081: Special case for realpath('nul') if normcase(path) == normcase(devnull): return '\\\\.\\NUL' - had_prefix = path.startswith(prefix) + had_prefix = path[:4] == prefix if not had_prefix and not isabs(path): path = join(cwd, path) try: @@ -759,10 +759,10 @@ def realpath(path, *, strict=False): # The path returned by _getfinalpathname will always start with \\?\ - # strip off that prefix unless it was already provided on the original # path. - if not had_prefix and path.startswith(prefix): + if not had_prefix and path[:4] == prefix: # For UNC paths, the prefix will actually be \\?\UNC\ # Handle that case as well. - if path.startswith(unc_prefix): + if path[:8] == unc_prefix: spath = new_unc_prefix + path[len(unc_prefix):] else: spath = path[len(prefix):] From 78929b0b3de9194355d5e17bbcd22d3722a85f21 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 11:34:04 +0100 Subject: [PATCH 07/46] Remove `isabs` calls --- Lib/posixpath.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 713a85fbc2606d..93c472beda6823 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -374,7 +374,8 @@ def normpath(path): def abspath(path): """Return an absolute path.""" path = os.fspath(path) - if not isabs(path): + sep = _get_sep(path) + if path[:1] != sep: if isinstance(path, bytes): cwd = os.getcwdb() else: @@ -405,7 +406,7 @@ def _joinrealpath(path, rest, strict, seen): curdir = '.' pardir = '..' - if isabs(rest): + if rest[:1] == sep: rest = rest[1:] path = sep From afc7bbcac0a5355fa12c0495d21b57efd6ff3701 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 12:02:04 +0100 Subject: [PATCH 08/46] Rename result of `splitroot` --- Lib/ntpath.py | 66 ++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 83f55b362b3f61..a3d1efa02c1dab 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -110,34 +110,36 @@ def join(path, *paths): try: if not paths: path[:0] + sep #23780: Ensure compatible data type even if p is null. - result_drive, result_root, result_path = splitroot(path) + result_drive, result_root, result_tail = splitroot(path) for p in map(os.fspath, paths): - p_drive, p_root, p_path = splitroot(p) + p_drive, p_root, p_tail = splitroot(p) if p_root: # Second path is absolute if p_drive or not result_drive: result_drive = p_drive result_root = p_root - result_path = p_path + result_tail = p_tail continue elif p_drive and p_drive != result_drive: if p_drive.lower() != result_drive.lower(): # Different drives => ignore the first path entirely result_drive = p_drive result_root = p_root - result_path = p_path + result_tail = p_tail continue # Same drive in different case result_drive = p_drive # Second path is relative to the first - if result_path and result_path[-1] not in seps: - result_path = result_path + sep - result_path = result_path + p_path + if result_tail and result_tail[-1] not in seps: + result_tail = result_tail + sep + result_tail = result_tail + p_tail ## add separator between UNC and non-absolute path - if (result_path and not result_root and - result_drive and result_drive[-1:] not in colon + seps): - return result_drive + sep + result_path - return result_drive + result_root + result_path + if ( + result_tail and not result_root + and result_drive and result_drive[-1] not in colon + seps + ): + return result_drive + sep + result_tail + return result_drive + result_root + result_tail except (TypeError, AttributeError, BytesWarning): genericpath._check_arg_types('join', path, *paths) raise @@ -233,13 +235,13 @@ def split(p): Either part may be empty.""" p = os.fspath(p) seps = _get_bothseps(p) - d, r, p = splitroot(p) + drive, root, tail = splitroot(p) # set i to index beyond p's last slash - i = len(p) - while i and p[i-1] not in seps: + i = len(tail) + while i and tail[i-1] not in seps: i -= 1 - head, tail = p[:i], p[i:] # now tail has no slashes - return d + r + head.rstrip(seps), tail + head, tail = tail[:i], tail[i:] # now tail has no slashes + return drive + root + head.rstrip(seps), tail # Split a path in root and extension. @@ -305,10 +307,10 @@ def ismount(path): path = os.fspath(path) seps = _get_bothseps(path) path = abspath(path) - drive, root, rest = splitroot(path) + drive, root, tail = splitroot(path) if drive and drive[0] in seps: - return not rest - if root and not rest: + return not tail + if root and not tail: return True if _getvolumepathname: @@ -555,9 +557,9 @@ def normpath(path): curdir = '.' pardir = '..' path = path.replace(altsep, sep) - drive, root, path = splitroot(path) + drive, root, tail = splitroot(path) prefix = drive + root - comps = path.split(sep) + comps = tail.split(sep) i = 0 while i < len(comps): if not comps[i] or comps[i] == curdir: @@ -763,14 +765,14 @@ def realpath(path, *, strict=False): # For UNC paths, the prefix will actually be \\?\UNC\ # Handle that case as well. if path[:8] == unc_prefix: - spath = new_unc_prefix + path[len(unc_prefix):] + spath = new_unc_prefix + path[8:] else: - spath = path[len(prefix):] + spath = path[4:] # Ensure that the non-prefixed path resolves to the same path try: if _getfinalpathname(spath) == path: path = spath - except ValueError as ex: + except ValueError: # Unexpected, as an invalid path should not have gained a prefix # at any point, but we ignore this error just in case. pass @@ -807,14 +809,14 @@ def relpath(path, start=None): try: start_abs = abspath(normpath(start)) path_abs = abspath(normpath(path)) - start_drive, _, start_rest = splitroot(start_abs) - path_drive, _, path_rest = splitroot(path_abs) + start_drive, _, start_tail = splitroot(start_abs) + path_drive, _, path_tail = splitroot(path_abs) if normcase(start_drive) != normcase(path_drive): raise ValueError("path is on mount %r, start on mount %r" % ( path_drive, start_drive)) - start_list = [x for x in start_rest.split(sep) if x] - path_list = [x for x in path_rest.split(sep) if x] + start_list = [x for x in start_tail.split(sep) if x] + path_list = [x for x in path_tail.split(sep) if x] # Work out how much of the filepath is shared by start and path. i = 0 for e1, e2 in zip(start_list, path_list): @@ -863,18 +865,18 @@ def commonpath(paths): # Check that all drive letters or UNC paths match. The check is made # only now otherwise type errors for mixing strings and bytes would not # be caught. - if len({d for d, _, _ in rootsplits}) != 1: + if len({drive for drive, _, _ in rootsplits}) != 1: raise ValueError("Paths don't have the same drive") - if len({r for _, r, _ in rootsplits}) != 1: + if len({root for _, root, _ in rootsplits}) != 1: raise ValueError("Can't mix absolute and relative paths") drive, root, tail = splitroot(paths[0].replace(altsep, sep)) common = [c for c in tail.split(sep) if c and c != curdir] split_paths = [ - [c for c in t.split(sep) if c and c != curdir] - for _, _, t in rootsplits + [c for c in tail.split(sep) if c and c != curdir] + for _, _, tail in rootsplits ] s1 = min(split_paths) s2 = max(split_paths) From f5bbaf66b90e3640153f336774738334842725d3 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 13:25:17 +0100 Subject: [PATCH 09/46] Refactor `os.path` --- Lib/ntpath.py | 164 ++++++++++++++++++++++------------------------- Lib/posixpath.py | 18 +++--- 2 files changed, 85 insertions(+), 97 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index a3d1efa02c1dab..eb75acb0c3b29f 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -47,7 +47,17 @@ def _get_bothseps(path): LCMapStringEx as _LCMapStringEx, LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE as _LCMAP_LOWERCASE) +except ImportError: + def normcase(s): + """Normalize case of pathname. + Makes all characters lowercase and all slashes into backslashes. + """ + s = os.fspath(s) + if isinstance(s, bytes): + return os.fsencode(os.fsdecode(s).replace('/', '\\').lower()) + return s.replace('/', '\\').lower() +else: def normcase(s): """Normalize case of pathname. @@ -66,16 +76,6 @@ def normcase(s): return _LCMapStringEx(_LOCALE_NAME_INVARIANT, _LCMAP_LOWERCASE, s.replace('/', '\\')) -except ImportError: - def normcase(s): - """Normalize case of pathname. - - Makes all characters lowercase and all slashes into backslashes. - """ - s = os.fspath(s) - if isinstance(s, bytes): - return os.fsencode(os.fsdecode(s).replace('/', '\\').lower()) - return s.replace('/', '\\').lower() def isabs(s): @@ -279,9 +279,9 @@ def isjunction(path): """Test whether a path is a junction""" try: st = os.lstat(path) + return st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT except (OSError, ValueError, AttributeError): return False - return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT) else: # Use genericpath.isjunction as imported above pass @@ -301,6 +301,8 @@ def isjunction(path): from nt import _getvolumepathname except ImportError: _getvolumepathname = None + + def ismount(path): """Test whether a path is a mount point (a drive root, the root of a share, or a mounted volume)""" @@ -312,24 +314,22 @@ def ismount(path): return not tail if root and not tail: return True - - if _getvolumepathname: - x = path.rstrip(seps) - y =_getvolumepathname(path).rstrip(seps) - return x.casefold() == y.casefold() - else: + if not _getvolumepathname: return False + x = path.rstrip(seps) + y = _getvolumepathname(path).rstrip(seps) + return x.casefold() == y.casefold() _reserved_chars = frozenset( - {chr(i) for i in range(32)} | - {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} + {chr(i) for i in range(32)} + | {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} ) _reserved_names = frozenset( - {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | - {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | - {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} + {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} + | {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} + | {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} ) def isreserved(path): @@ -352,9 +352,7 @@ def _isreservedname(name): # DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules # are complex and vary across Windows versions. On the side of # caution, return True for names that may not be reserved. - if name.partition('.')[0].rstrip(' ').upper() in _reserved_names: - return True - return False + return name.partition('.')[0].rstrip(' ').upper() in _reserved_names # Expand paths beginning with '~' or '~user'. @@ -477,19 +475,17 @@ def expandvars(path): pathlen = len(path) try: index = path.index(percent) + var = path[:index] + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + res += value except ValueError: res += percent + path index = pathlen - 1 - else: - var = path[:index] - try: - if environ is None: - value = os.fsencode(os.environ[os.fsdecode(var)]) - else: - value = environ[var] - except KeyError: - value = percent + var + percent - res += value + except KeyError: + res += percent + var + percent elif c == dollar: # variable or '$$' if path[index + 1:index + 2] == dollar: res += c @@ -499,19 +495,17 @@ def expandvars(path): pathlen = len(path) try: index = path.index(rbrace) + var = path[:index] + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + res += value except ValueError: res += dollar + brace + path index = pathlen - 1 - else: - var = path[:index] - try: - if environ is None: - value = os.fsencode(os.environ[os.fsdecode(var)]) - else: - value = environ[var] - except KeyError: - value = dollar + brace + var + rbrace - res += value + except KeyError: + res += dollar + brace + var + rbrace else: var = path[:0] index += 1 @@ -525,9 +519,9 @@ def expandvars(path): value = os.fsencode(os.environ[os.fsdecode(var)]) else: value = environ[var] + res += value except KeyError: - value = dollar + var - res += value + res += dollar + var if c: index -= 1 else: @@ -542,6 +536,12 @@ def expandvars(path): try: from nt import _path_normpath + def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." + return _path_normpath(path) or "." except ImportError: def normpath(path): """Normalize path, eliminating double slashes, etc.""" @@ -564,14 +564,13 @@ def normpath(path): while i < len(comps): if not comps[i] or comps[i] == curdir: del comps[i] - elif comps[i] == pardir: - if i > 0 and comps[i-1] != pardir: - del comps[i-1:i+1] - i -= 1 - elif i == 0 and root: - del comps[i] - else: - i += 1 + elif comps[i] != pardir: + i += 1 + elif i > 0 and comps[i-1] != pardir: + del comps[i-1:i+1] + i -= 1 + elif i == 0 and root: + del comps[i] else: i += 1 # If the path is now empty, substitute '.' @@ -579,14 +578,6 @@ def normpath(path): comps.append(curdir) return prefix + sep.join(comps) -else: - def normpath(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." - return _path_normpath(path) or "." - def _abspath_fallback(path): """Return the absolute version of a path as a fallback function in case @@ -607,10 +598,8 @@ def _abspath_fallback(path): # Return an absolute path. try: from nt import _getfullpathname - except ImportError: # not running on Windows - mock up something sensible abspath = _abspath_fallback - else: # use native Windows method on Windows def abspath(path): """Return the absolute version of a path.""" @@ -697,28 +686,28 @@ def _getfinalpathname_nonstrict(path): except OSError as ex: if ex.winerror not in allowed_winerror: raise + try: + # The OS could not resolve this path fully, so we attempt + # to follow the link ourselves. If we succeed, join the tail + # and return. + new_path = _readlink_deep(path) + if new_path != path: + return join(new_path, tail) if tail else new_path + except OSError: + # If we fail to readlink(), let's keep traversing + pass + # If we get these errors, try to get the real name of the file without accessing it. + if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921): try: - # The OS could not resolve this path fully, so we attempt - # to follow the link ourselves. If we succeed, join the tail - # and return. - new_path = _readlink_deep(path) - if new_path != path: - return join(new_path, tail) if tail else new_path + name = _findfirstfile(path) + path, _ = split(path) except OSError: - # If we fail to readlink(), let's keep traversing - pass - # If we get these errors, try to get the real name of the file without accessing it. - if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921): - try: - name = _findfirstfile(path) - path, _ = split(path) - except OSError: - path, name = split(path) - else: path, name = split(path) - if path and not name: - return path + tail - tail = join(name, tail) if tail else name + else: + path, name = split(path) + if path and not name: + return path + tail + tail = join(name, tail) if tail else name return tail def realpath(path, *, strict=False): @@ -904,12 +893,13 @@ def commonpath(paths): try: from nt import _path_isdevdrive +except ImportError: + # Use genericpath.isdevdrive as imported above + pass +else: def isdevdrive(path): """Determines whether the specified path is on a Windows Dev Drive.""" try: return _path_isdevdrive(abspath(path)) except OSError: return False -except ImportError: - # Use genericpath.isdevdrive as imported above - pass diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 93c472beda6823..7643a7f1052754 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -117,12 +117,9 @@ def split(p): def splitext(p): p = os.fspath(p) if isinstance(p, bytes): - sep = b'/' - extsep = b'.' + return genericpath._splitext(p, b'/', None, b'.') else: - sep = '/' - extsep = '.' - return genericpath._splitext(p, sep, None, extsep) + return genericpath._splitext(p, '/', None, '.') splitext.__doc__ = genericpath._splitext.__doc__ # Split a pathname into a drive specification and the rest of the @@ -248,9 +245,7 @@ def expanduser(path): # bpo-10496: if the user name from the path doesn't exist in the # password database, return the path unchanged return path - elif 'HOME' in os.environ: - userhome = os.environ['HOME'] - else: + elif 'HOME' not in os.environ: try: import pwd userhome = pwd.getpwuid(os.getuid()).pw_dir @@ -259,6 +254,8 @@ def expanduser(path): # bpo-10496: if the current user identifier doesn't exist in the # password database, return the path unchanged return path + else: + userhome = os.environ['HOME'] # if no user home, return the path unchanged on VxWorks if userhome is None and sys.platform == "vxworks": @@ -361,14 +358,15 @@ def _normpath_fallback(path): try: from posix import _path_normpath +except ImportError: + normpath = _normpath_fallback +else: def normpath(path): """Normalize path, eliminating double slashes, etc.""" path = os.fspath(path) if isinstance(path, bytes): return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." return _path_normpath(path) or "." -except ImportError: - normpath = _normpath_fallback def abspath(path): From e740f1011902bd25591a6d222c6a34ff57558e2f Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 13:54:39 +0100 Subject: [PATCH 10/46] fix unbound variable --- Lib/ntpath.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index eb75acb0c3b29f..ed86eb8229a981 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -684,7 +684,8 @@ def _getfinalpathname_nonstrict(path): path = _getfinalpathname(path) return join(path, tail) if tail else path except OSError as ex: - if ex.winerror not in allowed_winerror: + winerror = ex.winerror + if winerror not in allowed_winerror: raise try: # The OS could not resolve this path fully, so we attempt @@ -697,7 +698,7 @@ def _getfinalpathname_nonstrict(path): # If we fail to readlink(), let's keep traversing pass # If we get these errors, try to get the real name of the file without accessing it. - if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921): + if winerror in (1, 5, 32, 50, 87, 1920, 1921): try: name = _findfirstfile(path) path, _ = split(path) From d02c7268b6ee1e8fcec8fcc4c6b529069162cccd Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 15:55:25 +0100 Subject: [PATCH 11/46] hardcode constants like documented --- Lib/genericpath.py | 6 +++--- Lib/ntpath.py | 33 +++++++++++++++------------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index ba7b0a13c7f81d..21857a1d41c96d 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -104,7 +104,8 @@ def getctime(filename): # Return the longest prefix of all list elements. def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" - if not m: return '' + if not m: + return '' # Some people pass in a list of pathname parts to operate in an OS-agnostic # fashion; don't try to translate in that case as that's an abuse of the # API and they are already doing what they need to be OS-agnostic and so @@ -122,8 +123,7 @@ def commonprefix(m): # describing the same file? def samestat(s1, s2): """Test whether two stat buffers reference the same file""" - return (s1.st_ino == s2.st_ino and - s1.st_dev == s2.st_dev) + return s1.st_ino == s2.st_ino and s1.st_dev == s2.st_dev # Are two filenames really pointing to the same file? diff --git a/Lib/ntpath.py b/Lib/ntpath.py index ed86eb8229a981..cc8fd60a746ad3 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -43,10 +43,9 @@ def _get_bothseps(path): # (this is done by normpath). try: - from _winapi import ( - LCMapStringEx as _LCMapStringEx, - LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, - LCMAP_LOWERCASE as _LCMAP_LOWERCASE) + from _winapi import LCMapStringEx as _LCMapStringEx + from _winapi import LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT + from _winapi import LCMAP_LOWERCASE as _LCMAP_LOWERCASE except ImportError: def normcase(s): """Normalize case of pathname. @@ -69,8 +68,7 @@ def normcase(s): if isinstance(s, bytes): encoding = sys.getfilesystemencoding() s = s.decode(encoding, 'surrogateescape').replace('/', '\\') - s = _LCMapStringEx(_LOCALE_NAME_INVARIANT, - _LCMAP_LOWERCASE, s) + s = _LCMapStringEx(_LOCALE_NAME_INVARIANT, _LCMAP_LOWERCASE, s) return s.encode(encoding, 'surrogateescape') else: return _LCMapStringEx(_LOCALE_NAME_INVARIANT, @@ -279,9 +277,9 @@ def isjunction(path): """Test whether a path is a junction""" try: st = os.lstat(path) - return st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT except (OSError, ValueError, AttributeError): return False + return st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT else: # Use genericpath.isjunction as imported above pass @@ -336,8 +334,8 @@ def isreserved(path): """Return true if the pathname is reserved by the system.""" # Refer to "Naming Files, Paths, and Namespaces": # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file - path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep) - return any(_isreservedname(name) for name in reversed(path.split(sep))) + path = os.fsdecode(splitroot(path)[2]).replace('/', '\\') + return any(_isreservedname(name) for name in reversed(path.split('\\'))) def _isreservedname(name): """Return true if the filename is reserved by the system.""" @@ -381,14 +379,11 @@ def expanduser(path): if 'USERPROFILE' in os.environ: userhome = os.environ['USERPROFILE'] - elif not 'HOMEPATH' in os.environ: - return path - else: - try: - drive = os.environ['HOMEDRIVE'] - except KeyError: - drive = '' + elif 'HOMEPATH' in os.environ: + drive = os.environ.get('HOMEDRIVE', '') userhome = join(drive, os.environ['HOMEPATH']) + else: + return path if i != 1: #~user target_user = path[1:i] @@ -719,7 +714,8 @@ def realpath(path, *, strict=False): new_unc_prefix = b'\\\\' cwd = os.getcwdb() # bpo-38081: Special case for realpath(b'nul') - if normcase(path) == normcase(os.fsencode(devnull)): + devnul = b'nul' + if normcase(path) == devnul: return b'\\\\.\\NUL' else: prefix = '\\\\?\\' @@ -727,7 +723,8 @@ def realpath(path, *, strict=False): new_unc_prefix = '\\\\' cwd = os.getcwd() # bpo-38081: Special case for realpath('nul') - if normcase(path) == normcase(devnull): + devnul = 'nul' + if normcase(path) == devnul: return '\\\\.\\NUL' had_prefix = path[:4] == prefix if not had_prefix and not isabs(path): From b7948976e0090b0716e3f311942b2c7655987850 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 15:04:14 +0000 Subject: [PATCH 12/46] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst new file mode 100644 index 00000000000000..a0b7624d12034d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst @@ -0,0 +1 @@ +Hardode constants in :mod:`ntpath`. From c2c04bf40f49cb3960e4396a872b06e6e7eb1533 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 16:05:32 +0100 Subject: [PATCH 13/46] Fix typo --- .../2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst index a0b7624d12034d..c46ff82dcd6d23 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst @@ -1 +1 @@ -Hardode constants in :mod:`ntpath`. +Hardcode constants in :mod:`ntpath`. From 7c9dcae461586ef97db04a467895efbb3da011c8 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 18:23:21 +0100 Subject: [PATCH 14/46] exclude stylistic-only changes --- Lib/genericpath.py | 6 +++--- Lib/ntpath.py | 26 +++++++++++++------------- Lib/posixpath.py | 7 ++----- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index 21857a1d41c96d..ba7b0a13c7f81d 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -104,8 +104,7 @@ def getctime(filename): # Return the longest prefix of all list elements. def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" - if not m: - return '' + if not m: return '' # Some people pass in a list of pathname parts to operate in an OS-agnostic # fashion; don't try to translate in that case as that's an abuse of the # API and they are already doing what they need to be OS-agnostic and so @@ -123,7 +122,8 @@ def commonprefix(m): # describing the same file? def samestat(s1, s2): """Test whether two stat buffers reference the same file""" - return s1.st_ino == s2.st_ino and s1.st_dev == s2.st_dev + return (s1.st_ino == s2.st_ino and + s1.st_dev == s2.st_dev) # Are two filenames really pointing to the same file? diff --git a/Lib/ntpath.py b/Lib/ntpath.py index cc8fd60a746ad3..49d33b66efa3ec 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -43,9 +43,10 @@ def _get_bothseps(path): # (this is done by normpath). try: - from _winapi import LCMapStringEx as _LCMapStringEx - from _winapi import LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT - from _winapi import LCMAP_LOWERCASE as _LCMAP_LOWERCASE + from _winapi import ( + LCMapStringEx as _LCMapStringEx, + LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, + LCMAP_LOWERCASE as _LCMAP_LOWERCASE) except ImportError: def normcase(s): """Normalize case of pathname. @@ -68,7 +69,8 @@ def normcase(s): if isinstance(s, bytes): encoding = sys.getfilesystemencoding() s = s.decode(encoding, 'surrogateescape').replace('/', '\\') - s = _LCMapStringEx(_LOCALE_NAME_INVARIANT, _LCMAP_LOWERCASE, s) + s = _LCMapStringEx(_LOCALE_NAME_INVARIANT, + _LCMAP_LOWERCASE, s) return s.encode(encoding, 'surrogateescape') else: return _LCMapStringEx(_LOCALE_NAME_INVARIANT, @@ -132,10 +134,8 @@ def join(path, *paths): result_tail = result_tail + sep result_tail = result_tail + p_tail ## add separator between UNC and non-absolute path - if ( - result_tail and not result_root - and result_drive and result_drive[-1] not in colon + seps - ): + if (result_tail and not result_root and + result_drive and result_drive[-1] not in colon + seps): return result_drive + sep + result_tail return result_drive + result_root + result_tail except (TypeError, AttributeError, BytesWarning): @@ -320,14 +320,14 @@ def ismount(path): _reserved_chars = frozenset( - {chr(i) for i in range(32)} - | {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} + {chr(i) for i in range(32)} | + {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} ) _reserved_names = frozenset( - {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} - | {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} - | {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} + {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | + {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | + {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} ) def isreserved(path): diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 7643a7f1052754..3102cfdb33bab4 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -346,11 +346,8 @@ def _normpath_fallback(path): for comp in tail.split(sep): if not comp or comp == curdir: continue - if ( - comp != pardir - or (not root and not comps) - or (comps and comps[-1] == pardir) - ): + if (comp != pardir or (not root and not comps) or + (comps and comps[-1] == pardir)): comps.append(comp) elif comps: comps.pop() From d35783c8272e3731f62e6025ed333d1bbc3f9eef Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 18:41:06 +0100 Subject: [PATCH 15/46] Revert renaming --- Lib/ntpath.py | 62 ++++++++++++++++++++++++------------------------ Lib/posixpath.py | 30 +++++++++++------------ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 49d33b66efa3ec..c5fa344e879386 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -110,34 +110,34 @@ def join(path, *paths): try: if not paths: path[:0] + sep #23780: Ensure compatible data type even if p is null. - result_drive, result_root, result_tail = splitroot(path) + result_drive, result_root, result_path = splitroot(path) for p in map(os.fspath, paths): - p_drive, p_root, p_tail = splitroot(p) + p_drive, p_root, p_path = splitroot(p) if p_root: # Second path is absolute if p_drive or not result_drive: result_drive = p_drive result_root = p_root - result_tail = p_tail + result_path = p_path continue elif p_drive and p_drive != result_drive: if p_drive.lower() != result_drive.lower(): # Different drives => ignore the first path entirely result_drive = p_drive result_root = p_root - result_tail = p_tail + result_path = p_path continue # Same drive in different case result_drive = p_drive # Second path is relative to the first - if result_tail and result_tail[-1] not in seps: - result_tail = result_tail + sep - result_tail = result_tail + p_tail + if result_path and result_path[-1] not in seps: + result_path = result_path + sep + result_path = result_path + p_path ## add separator between UNC and non-absolute path - if (result_tail and not result_root and + if (result_path and not result_root and result_drive and result_drive[-1] not in colon + seps): - return result_drive + sep + result_tail - return result_drive + result_root + result_tail + return result_drive + sep + result_path + return result_drive + result_root + result_path except (TypeError, AttributeError, BytesWarning): genericpath._check_arg_types('join', path, *paths) raise @@ -233,13 +233,13 @@ def split(p): Either part may be empty.""" p = os.fspath(p) seps = _get_bothseps(p) - drive, root, tail = splitroot(p) + d, r, p = splitroot(p) # set i to index beyond p's last slash - i = len(tail) - while i and tail[i-1] not in seps: + i = len(p) + while i and p[i-1] not in seps: i -= 1 - head, tail = tail[:i], tail[i:] # now tail has no slashes - return drive + root + head.rstrip(seps), tail + head, p = p[:i], p[i:] # now tail has no slashes + return d + r + head.rstrip(seps), p # Split a path in root and extension. @@ -307,10 +307,10 @@ def ismount(path): path = os.fspath(path) seps = _get_bothseps(path) path = abspath(path) - drive, root, tail = splitroot(path) + drive, root, rest = splitroot(path) if drive and drive[0] in seps: - return not tail - if root and not tail: + return not rest + if root and not rest: return True if not _getvolumepathname: return False @@ -552,9 +552,9 @@ def normpath(path): curdir = '.' pardir = '..' path = path.replace(altsep, sep) - drive, root, tail = splitroot(path) + drive, root, path = splitroot(path) prefix = drive + root - comps = tail.split(sep) + comps = path.split(sep) i = 0 while i < len(comps): if not comps[i] or comps[i] == curdir: @@ -796,14 +796,14 @@ def relpath(path, start=None): try: start_abs = abspath(normpath(start)) path_abs = abspath(normpath(path)) - start_drive, _, start_tail = splitroot(start_abs) - path_drive, _, path_tail = splitroot(path_abs) + start_drive, _, start_rest = splitroot(start_abs) + path_drive, _, path_rest = splitroot(path_abs) if normcase(start_drive) != normcase(path_drive): raise ValueError("path is on mount %r, start on mount %r" % ( path_drive, start_drive)) - start_list = [x for x in start_tail.split(sep) if x] - path_list = [x for x in path_tail.split(sep) if x] + start_list = [x for x in start_rest.split(sep) if x] + path_list = [x for x in path_rest.split(sep) if x] # Work out how much of the filepath is shared by start and path. i = 0 for e1, e2 in zip(start_list, path_list): @@ -847,23 +847,23 @@ def commonpath(paths): curdir = '.' try: - rootsplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] + drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] # Check that all drive letters or UNC paths match. The check is made # only now otherwise type errors for mixing strings and bytes would not # be caught. - if len({drive for drive, _, _ in rootsplits}) != 1: + if len({d for d, _, _ in drivesplits}) != 1: raise ValueError("Paths don't have the same drive") - if len({root for _, root, _ in rootsplits}) != 1: + if len({r for _, r, _ in drivesplits}) != 1: raise ValueError("Can't mix absolute and relative paths") - drive, root, tail = splitroot(paths[0].replace(altsep, sep)) - common = [c for c in tail.split(sep) if c and c != curdir] + drive, root, path = splitroot(paths[0].replace(altsep, sep)) + common = [c for c in path.split(sep) if c and c != curdir] split_paths = [ - [c for c in tail.split(sep) if c and c != curdir] - for _, _, tail in rootsplits + [c for c in p.split(sep) if c and c != curdir] + for _, _, p in drivesplits ] s1 = min(split_paths) s2 = max(split_paths) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 3102cfdb33bab4..7965ba045692a9 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -333,25 +333,25 @@ def _normpath_fallback(path): path = os.fspath(path) if isinstance(path, bytes): sep = b'/' - curdir = b'.' - pardir = b'..' + dot = b'.' + dotdot = b'..' else: sep = '/' - curdir = '.' - pardir = '..' + dot = '.' + dotdot = '..' if not path: - return curdir - _, root, tail = splitroot(path) - comps = [] - for comp in tail.split(sep): - if not comp or comp == curdir: + return dot + _, initial_slashes, path = splitroot(path) + new_comps = [] + for comp in path.split(sep): + if not comp or comp == dot: continue - if (comp != pardir or (not root and not comps) or - (comps and comps[-1] == pardir)): - comps.append(comp) - elif comps: - comps.pop() - return (root + sep.join(comps)) or curdir + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + return (initial_slashes + sep.join(new_comps)) or dot try: from posix import _path_normpath From eb247237d390e938ffab9673f417c4f3bc745330 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 19:55:11 +0100 Subject: [PATCH 16/46] Revert unnesting --- Lib/ntpath.py | 91 +++++++++++++++++++++++++----------------------- Lib/posixpath.py | 15 ++++---- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index c5fa344e879386..72a376b43499e0 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -47,17 +47,6 @@ def _get_bothseps(path): LCMapStringEx as _LCMapStringEx, LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE as _LCMAP_LOWERCASE) -except ImportError: - def normcase(s): - """Normalize case of pathname. - - Makes all characters lowercase and all slashes into backslashes. - """ - s = os.fspath(s) - if isinstance(s, bytes): - return os.fsencode(os.fsdecode(s).replace('/', '\\').lower()) - return s.replace('/', '\\').lower() -else: def normcase(s): """Normalize case of pathname. @@ -76,6 +65,16 @@ def normcase(s): return _LCMapStringEx(_LOCALE_NAME_INVARIANT, _LCMAP_LOWERCASE, s.replace('/', '\\')) +except ImportError: + def normcase(s): + """Normalize case of pathname. + + Makes all characters lowercase and all slashes into backslashes. + """ + s = os.fspath(s) + if isinstance(s, bytes): + return os.fsencode(os.fsdecode(s).replace('/', '\\').lower()) + return s.replace('/', '\\').lower() def isabs(s): @@ -238,8 +237,8 @@ def split(p): i = len(p) while i and p[i-1] not in seps: i -= 1 - head, p = p[:i], p[i:] # now tail has no slashes - return d + r + head.rstrip(seps), p + head, tail = p[:i], p[i:] # now tail has no slashes + return d + r + head.rstrip(seps), tail # Split a path in root and extension. @@ -299,8 +298,6 @@ def isjunction(path): from nt import _getvolumepathname except ImportError: _getvolumepathname = None - - def ismount(path): """Test whether a path is a mount point (a drive root, the root of a share, or a mounted volume)""" @@ -312,11 +309,13 @@ def ismount(path): return not rest if root and not rest: return True - if not _getvolumepathname: + + if _getvolumepathname: + x = path.rstrip(seps) + y = _getvolumepathname(path).rstrip(seps) + return x.casefold() == y.casefold() + else: return False - x = path.rstrip(seps) - y = _getvolumepathname(path).rstrip(seps) - return x.casefold() == y.casefold() _reserved_chars = frozenset( @@ -379,11 +378,11 @@ def expanduser(path): if 'USERPROFILE' in os.environ: userhome = os.environ['USERPROFILE'] - elif 'HOMEPATH' in os.environ: + elif 'HOMEPATH' not in os.environ: + return path + else: drive = os.environ.get('HOMEDRIVE', '') userhome = join(drive, os.environ['HOMEPATH']) - else: - return path if i != 1: #~user target_user = path[1:i] @@ -531,12 +530,6 @@ def expandvars(path): try: from nt import _path_normpath - def normpath(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." - return _path_normpath(path) or "." except ImportError: def normpath(path): """Normalize path, eliminating double slashes, etc.""" @@ -559,19 +552,27 @@ def normpath(path): while i < len(comps): if not comps[i] or comps[i] == curdir: del comps[i] - elif comps[i] != pardir: - i += 1 - elif i > 0 and comps[i-1] != pardir: - del comps[i-1:i+1] - i -= 1 - elif i == 0 and root: - del comps[i] + elif comps[i] == pardir: + if i > 0 and comps[i-1] != pardir: + del comps[i-1:i+1] + i -= 1 + elif i == 0 and root: + del comps[i] + else: + i += 1 else: i += 1 # If the path is now empty, substitute '.' if not prefix and not comps: comps.append(curdir) return prefix + sep.join(comps) +else: + def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + return os.fsencode(_path_normpath(os.fsdecode(path))) or b"." + return _path_normpath(path) or "." def _abspath_fallback(path): @@ -593,8 +594,10 @@ def _abspath_fallback(path): # Return an absolute path. try: from nt import _getfullpathname + except ImportError: # not running on Windows - mock up something sensible abspath = _abspath_fallback + else: # use native Windows method on Windows def abspath(path): """Return the absolute version of a path.""" @@ -849,14 +852,15 @@ def commonpath(paths): try: drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] - # Check that all drive letters or UNC paths match. The check is made - # only now otherwise type errors for mixing strings and bytes would not - # be caught. + # Check that absolute and relative paths aren't mixed. The check is + # made only now otherwise type errors for mixing strings and bytes + # would not be caught. + if len({r for _, r, _ in drivesplits}) != 1: + raise ValueError("Can't mix absolute and relative paths") + if len({d for d, _, _ in drivesplits}) != 1: raise ValueError("Paths don't have the same drive") - if len({r for _, r, _ in drivesplits}) != 1: - raise ValueError("Can't mix absolute and relative paths") drive, root, path = splitroot(paths[0].replace(altsep, sep)) common = [c for c in path.split(sep) if c and c != curdir] @@ -891,13 +895,12 @@ def commonpath(paths): try: from nt import _path_isdevdrive -except ImportError: - # Use genericpath.isdevdrive as imported above - pass -else: def isdevdrive(path): """Determines whether the specified path is on a Windows Dev Drive.""" try: return _path_isdevdrive(abspath(path)) except OSError: return False +except ImportError: + # Use genericpath.isdevdrive as imported above + pass diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 7965ba045692a9..65d6c019a77aad 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -315,12 +315,13 @@ def expandvars(path): value = os.fsencode(os.environ[os.fsdecode(name)]) else: value = environ[name] + except KeyError: + i = j + else: tail = path[j:] path = path[:i] + value i = len(path) path += tail - except KeyError: - i = j return path @@ -425,11 +426,12 @@ def _joinrealpath(path, rest, strict, seen): newpath = join(path, name) try: st = os.lstat(newpath) - is_link = stat.S_ISLNK(st.st_mode) except OSError: if strict: raise is_link = False + else: + is_link = stat.S_ISLNK(st.st_mode) if not is_link: path = newpath continue @@ -441,11 +443,12 @@ def _joinrealpath(path, rest, strict, seen): # use cached value continue # The symlink is not resolved, so we must have a symlink loop. - if not strict: + if strict: + # Raise OSError(errno.ELOOP) + os.stat(newpath) + else: # Return already resolved part + rest of the path unchanged. return join(newpath, rest), False - # Raise OSError(errno.ELOOP) - os.stat(newpath) seen[newpath] = None # not resolved symlink path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen) if not ok: From 5ca9ea31e756560f4785097237dcfd8273173bf0 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 20:42:56 +0100 Subject: [PATCH 17/46] Revert further changes --- Lib/ntpath.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 72a376b43499e0..d8a3a3e45e50a3 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -47,6 +47,7 @@ def _get_bothseps(path): LCMapStringEx as _LCMapStringEx, LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE as _LCMAP_LOWERCASE) + def normcase(s): """Normalize case of pathname. @@ -129,12 +130,14 @@ def join(path, *paths): # Same drive in different case result_drive = p_drive # Second path is relative to the first - if result_path and result_path[-1] not in seps: + # gh-117349: Indexing bytes is not correct here + if result_path and result_path[-1:] not in seps: result_path = result_path + sep result_path = result_path + p_path ## add separator between UNC and non-absolute path + ## Indexing bytes is not correct here if (result_path and not result_root and - result_drive and result_drive[-1] not in colon + seps): + result_drive and result_drive[-1:] not in colon + seps): return result_drive + sep + result_path return result_drive + result_root + result_path except (TypeError, AttributeError, BytesWarning): @@ -312,7 +315,7 @@ def ismount(path): if _getvolumepathname: x = path.rstrip(seps) - y = _getvolumepathname(path).rstrip(seps) + y =_getvolumepathname(path).rstrip(seps) return x.casefold() == y.casefold() else: return False @@ -566,6 +569,7 @@ def normpath(path): if not prefix and not comps: comps.append(curdir) return prefix + sep.join(comps) + else: def normpath(path): """Normalize path, eliminating double slashes, etc.""" @@ -762,7 +766,7 @@ def realpath(path, *, strict=False): try: if _getfinalpathname(spath) == path: path = spath - except ValueError: + except ValueError as ex: # Unexpected, as an invalid path should not have gained a prefix # at any point, but we ignore this error just in case. pass @@ -855,10 +859,10 @@ def commonpath(paths): # Check that absolute and relative paths aren't mixed. The check is # made only now otherwise type errors for mixing strings and bytes # would not be caught. - if len({r for _, r, _ in drivesplits}) != 1: + if len({r for d, r, p in drivesplits}) != 1: raise ValueError("Can't mix absolute and relative paths") - if len({d for d, _, _ in drivesplits}) != 1: + if len({d for d, r, p in drivesplits}) != 1: raise ValueError("Paths don't have the same drive") @@ -867,7 +871,7 @@ def commonpath(paths): split_paths = [ [c for c in p.split(sep) if c and c != curdir] - for _, _, p in drivesplits + for d, r, p in drivesplits ] s1 = min(split_paths) s2 = max(split_paths) From 3e4d0e33ef5e92f2acfdda660936e6badee4c231 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 20:46:43 +0100 Subject: [PATCH 18/46] Revert renaming --- Lib/posixpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 65d6c019a77aad..3e4980f6ee1a9b 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -387,7 +387,7 @@ def realpath(filename, *, strict=False): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" filename = os.fspath(filename) - path, _ = _joinrealpath(filename[:0], filename, strict, {}) + path, ok = _joinrealpath(filename[:0], filename, strict, {}) return abspath(path) # Join two paths, normalizing and eliminating any symbolic links encountered in From 72babadc2284e07e5fa84161a52fe0dde05937d9 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 29 Mar 2024 21:12:03 +0100 Subject: [PATCH 19/46] Remove newline --- Lib/ntpath.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index d8a3a3e45e50a3..1b8ea2817f472a 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -865,7 +865,6 @@ def commonpath(paths): if len({d for d, r, p in drivesplits}) != 1: raise ValueError("Paths don't have the same drive") - drive, root, path = splitroot(paths[0].replace(altsep, sep)) common = [c for c in path.split(sep) if c and c != curdir] From abfe46c8f50e76d7aa2ec3a36d698c324a390720 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 09:14:56 +0100 Subject: [PATCH 20/46] Remove slice --- Lib/ntpath.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 1b8ea2817f472a..d51dfdca4c251f 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -130,14 +130,12 @@ def join(path, *paths): # Same drive in different case result_drive = p_drive # Second path is relative to the first - # gh-117349: Indexing bytes is not correct here - if result_path and result_path[-1:] not in seps: + if result_path and result_path[-1] not in seps: result_path = result_path + sep result_path = result_path + p_path ## add separator between UNC and non-absolute path - ## Indexing bytes is not correct here if (result_path and not result_root and - result_drive and result_drive[-1:] not in colon + seps): + result_drive and result_drive[-1] not in colon + seps): return result_drive + sep + result_path return result_drive + result_root + result_path except (TypeError, AttributeError, BytesWarning): From 2a055bac08dd23ccb9f632013a389ec707144ef3 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 10:13:51 +0100 Subject: [PATCH 21/46] Speedup `posixpath.ismount` --- Lib/posixpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 3e4980f6ee1a9b..4836373b6110c1 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -199,7 +199,7 @@ def ismount(path): if stat.S_ISLNK(s1.st_mode): return False - parent = realpath(dirname(path)) + parent = dirname(abspath(path)) try: s2 = os.lstat(parent) except (OSError, ValueError): From 759b189ae190e426b727fc0c66519dd41f403dcb Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Sat, 30 Mar 2024 11:15:31 +0100 Subject: [PATCH 22/46] Update Lib/posixpath.py Co-authored-by: Alex Waygood --- Lib/posixpath.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 4836373b6110c1..46085c98b41e75 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -390,8 +390,9 @@ def realpath(filename, *, strict=False): path, ok = _joinrealpath(filename[:0], filename, strict, {}) return abspath(path) -# Join two paths, normalizing and eliminating any symbolic links encountered in -# the second path. Two leading slashes are replaced by a single slash. +# Join two paths, normalizing and eliminating any symbolic links +# encountered in the second path. +# Two leading slashes are replaced by a single slash. def _joinrealpath(path, rest, strict, seen): if isinstance(path, bytes): sep = b'/' From cca16ba3c33550425c9bb25fbaf1bf74b409dad2 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 13:55:18 +0100 Subject: [PATCH 23/46] Revert unnesting --- Lib/ntpath.py | 8 ++++---- Lib/posixpath.py | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 1b8ea2817f472a..b565e062d6e590 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -721,8 +721,8 @@ def realpath(path, *, strict=False): new_unc_prefix = b'\\\\' cwd = os.getcwdb() # bpo-38081: Special case for realpath(b'nul') - devnul = b'nul' - if normcase(path) == devnul: + devnull = b'nul' + if normcase(path) == devnull: return b'\\\\.\\NUL' else: prefix = '\\\\?\\' @@ -730,8 +730,8 @@ def realpath(path, *, strict=False): new_unc_prefix = '\\\\' cwd = os.getcwd() # bpo-38081: Special case for realpath('nul') - devnul = 'nul' - if normcase(path) == devnul: + devnull = 'nul' + if normcase(path) == devnull: return '\\\\.\\NUL' had_prefix = path[:4] == prefix if not had_prefix and not isabs(path): diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 3e4980f6ee1a9b..6e19503a062325 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -194,10 +194,10 @@ def ismount(path): except (OSError, ValueError): # It doesn't exist -- so not a mount point. :-) return False - - # A symlink can never be a mount point - if stat.S_ISLNK(s1.st_mode): - return False + else: + # A symlink can never be a mount point + if stat.S_ISLNK(s1.st_mode): + return False parent = realpath(dirname(path)) try: @@ -232,7 +232,19 @@ def expanduser(path): i = path.find(sep, 1) if i < 0: i = len(path) - if i != 1: + if i == 1: + if 'HOME' not in os.environ: + try: + import pwd + userhome = pwd.getpwuid(os.getuid()).pw_dir + except (ImportError, KeyError): + # pwd module unavailable, return path unchanged + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path + else: + userhome = os.environ['HOME'] + else: try: import pwd name = path[1:i] @@ -245,17 +257,6 @@ def expanduser(path): # bpo-10496: if the user name from the path doesn't exist in the # password database, return the path unchanged return path - elif 'HOME' not in os.environ: - try: - import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir - except (ImportError, KeyError): - # pwd module unavailable, return path unchanged - # bpo-10496: if the current user identifier doesn't exist in the - # password database, return the path unchanged - return path - else: - userhome = os.environ['HOME'] # if no user home, return the path unchanged on VxWorks if userhome is None and sys.platform == "vxworks": @@ -356,6 +357,7 @@ def _normpath_fallback(path): try: from posix import _path_normpath + except ImportError: normpath = _normpath_fallback else: From 581862efa288cd3186c5a7c13119e919c28f5dc8 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 14:04:41 +0100 Subject: [PATCH 24/46] Remove line breaks --- Lib/posixpath.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 4a2162a8ddc090..1527a01f113b99 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -250,14 +250,12 @@ def expanduser(path): name = path[1:i] if isinstance(name, bytes): name = name.decode('ascii') - userhome = pwd.getpwnam(name).pw_dir except (ImportError, KeyError): # pwd module unavailable, return path unchanged # bpo-10496: if the user name from the path doesn't exist in the # password database, return the path unchanged return path - # if no user home, return the path unchanged on VxWorks if userhome is None and sys.platform == "vxworks": return path From d2987fd363bfdc01d1298bce9a0e903dac2786b4 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 15:10:40 +0100 Subject: [PATCH 25/46] Revert `ntpath.expandvars` --- Lib/ntpath.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 36f99950fb90fa..855f6f7de08441 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -470,17 +470,19 @@ def expandvars(path): pathlen = len(path) try: index = path.index(percent) - var = path[:index] - if environ is None: - value = os.fsencode(os.environ[os.fsdecode(var)]) - else: - value = environ[var] - res += value except ValueError: res += percent + path index = pathlen - 1 - except KeyError: - res += percent + var + percent + else: + var = path[:index] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + except KeyError: + value = percent + var + percent + res += value elif c == dollar: # variable or '$$' if path[index + 1:index + 2] == dollar: res += c @@ -490,17 +492,19 @@ def expandvars(path): pathlen = len(path) try: index = path.index(rbrace) - var = path[:index] - if environ is None: - value = os.fsencode(os.environ[os.fsdecode(var)]) - else: - value = environ[var] - res += value except ValueError: res += dollar + brace + path index = pathlen - 1 - except KeyError: - res += dollar + brace + var + rbrace + else: + var = path[:index] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + except KeyError: + value = dollar + brace + var + rbrace + res += value else: var = path[:0] index += 1 @@ -514,9 +518,9 @@ def expandvars(path): value = os.fsencode(os.environ[os.fsdecode(var)]) else: value = environ[var] - res += value except KeyError: - res += dollar + var + value = dollar + var + res += value if c: index -= 1 else: From 1866544a847417aa5f14aba01bda24ef903e0808 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 15:34:30 +0100 Subject: [PATCH 26/46] Unexpose `posixpath._normpath_fallback` --- Lib/ntpath.py | 6 ++---- Lib/posixpath.py | 50 +++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 855f6f7de08441..69ec306cebb366 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -723,8 +723,7 @@ def realpath(path, *, strict=False): new_unc_prefix = b'\\\\' cwd = os.getcwdb() # bpo-38081: Special case for realpath(b'nul') - devnull = b'nul' - if normcase(path) == devnull: + if normcase(path) == b'nul': return b'\\\\.\\NUL' else: prefix = '\\\\?\\' @@ -732,8 +731,7 @@ def realpath(path, *, strict=False): new_unc_prefix = '\\\\' cwd = os.getcwd() # bpo-38081: Special case for realpath('nul') - devnull = 'nul' - if normcase(path) == devnull: + if normcase(path) == 'nul': return '\\\\.\\NUL' had_prefix = path[:4] == prefix if not had_prefix and not isabs(path): diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 1527a01f113b99..dfd94ba99617d9 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -328,36 +328,34 @@ def expandvars(path): # It should be understood that this may change the meaning of the path # if it contains symbolic links! -def _normpath_fallback(path): - """Normalize path, eliminating double slashes, etc.""" - path = os.fspath(path) - if isinstance(path, bytes): - sep = b'/' - dot = b'.' - dotdot = b'..' - else: - sep = '/' - dot = '.' - dotdot = '..' - if not path: - return dot - _, initial_slashes, path = splitroot(path) - new_comps = [] - for comp in path.split(sep): - if not comp or comp == dot: - continue - if (comp != dotdot or (not initial_slashes and not new_comps) or - (new_comps and new_comps[-1] == dotdot)): - new_comps.append(comp) - elif new_comps: - new_comps.pop() - return (initial_slashes + sep.join(new_comps)) or dot - try: from posix import _path_normpath except ImportError: - normpath = _normpath_fallback + def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'/' + dot = b'.' + dotdot = b'..' + else: + sep = '/' + dot = '.' + dotdot = '..' + if not path: + return dot + _, initial_slashes, path = splitroot(path) + new_comps = [] + for comp in path.split(sep): + if not comp or comp == dot: + continue + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + return (initial_slashes + sep.join(new_comps)) or dot else: def normpath(path): """Normalize path, eliminating double slashes, etc.""" From 4a011235f63e01a7cb2cb7edb78a853bb0982f47 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Sat, 30 Mar 2024 15:44:52 +0100 Subject: [PATCH 27/46] Update Lib/posixpath.py Co-authored-by: Alex Waygood --- Lib/posixpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index dfd94ba99617d9..7053a819a94b9d 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -351,7 +351,7 @@ def normpath(path): if not comp or comp == dot: continue if (comp != dotdot or (not initial_slashes and not new_comps) or - (new_comps and new_comps[-1] == dotdot)): + (new_comps and new_comps[-1] == dotdot)): new_comps.append(comp) elif new_comps: new_comps.pop() From 67f0620f764d9770025f1cd27c448310ce3f5c3a Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 15:54:11 +0100 Subject: [PATCH 28/46] Indent for git blame --- Lib/ntpath.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 69ec306cebb366..157714a262c4d5 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -691,28 +691,29 @@ def _getfinalpathname_nonstrict(path): winerror = ex.winerror if winerror not in allowed_winerror: raise - try: - # The OS could not resolve this path fully, so we attempt - # to follow the link ourselves. If we succeed, join the tail - # and return. - new_path = _readlink_deep(path) - if new_path != path: - return join(new_path, tail) if tail else new_path - except OSError: - # If we fail to readlink(), let's keep traversing - pass - # If we get these errors, try to get the real name of the file without accessing it. - if winerror in (1, 5, 32, 50, 87, 1920, 1921): + if True: # Indent for git blame try: - name = _findfirstfile(path) - path, _ = split(path) + # The OS could not resolve this path fully, so we attempt + # to follow the link ourselves. If we succeed, join the tail + # and return. + new_path = _readlink_deep(path) + if new_path != path: + return join(new_path, tail) if tail else new_path except OSError: + # If we fail to readlink(), let's keep traversing + pass + # If we get these errors, try to get the real name of the file without accessing it. + if winerror in (1, 5, 32, 50, 87, 1920, 1921): + try: + name = _findfirstfile(path) + path, _ = split(path) + except OSError: + path, name = split(path) + else: path, name = split(path) - else: - path, name = split(path) - if path and not name: - return path + tail - tail = join(name, tail) if tail else name + if path and not name: + return path + tail + tail = join(name, tail) if tail else name return tail def realpath(path, *, strict=False): From 9d18f8c98f2b30fe4f0c2dd4a9a42a2c18df83e0 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 16:36:37 +0100 Subject: [PATCH 29/46] Move back in except --- Lib/ntpath.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 157714a262c4d5..34e7aed6361c46 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -688,10 +688,8 @@ def _getfinalpathname_nonstrict(path): path = _getfinalpathname(path) return join(path, tail) if tail else path except OSError as ex: - winerror = ex.winerror - if winerror not in allowed_winerror: + if ex.winerror not in allowed_winerror: raise - if True: # Indent for git blame try: # The OS could not resolve this path fully, so we attempt # to follow the link ourselves. If we succeed, join the tail @@ -703,7 +701,7 @@ def _getfinalpathname_nonstrict(path): # If we fail to readlink(), let's keep traversing pass # If we get these errors, try to get the real name of the file without accessing it. - if winerror in (1, 5, 32, 50, 87, 1920, 1921): + if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921): try: name = _findfirstfile(path) path, _ = split(path) From 9520cc64c51d5b52bb39c9dbc2a9a491a16f9259 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 16:39:24 +0100 Subject: [PATCH 30/46] Revert changing comment --- Lib/ntpath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 34e7aed6361c46..b42877ba64b17b 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -855,12 +855,12 @@ def commonpath(paths): try: drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] - # Check that absolute and relative paths aren't mixed. The check is - # made only now otherwise type errors for mixing strings and bytes - # would not be caught. if len({r for d, r, p in drivesplits}) != 1: raise ValueError("Can't mix absolute and relative paths") + # Check that all drive letters or UNC paths match. The check is made only + # now otherwise type errors for mixing strings and bytes would not be + # caught. if len({d for d, r, p in drivesplits}) != 1: raise ValueError("Paths don't have the same drive") From de4b09e698578f8ac2bfe8c7662ffbc18b8c0294 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 17:01:28 +0100 Subject: [PATCH 31/46] Split try except blocks --- Lib/posixpath.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 7053a819a94b9d..cd4c17453d95b4 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -236,9 +236,12 @@ def expanduser(path): if 'HOME' not in os.environ: try: import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir - except (ImportError, KeyError): + except ImportError: # pwd module unavailable, return path unchanged + return path + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: # bpo-10496: if the current user identifier doesn't exist in the # password database, return the path unchanged return path @@ -247,12 +250,15 @@ def expanduser(path): else: try: import pwd - name = path[1:i] - if isinstance(name, bytes): - name = name.decode('ascii') - userhome = pwd.getpwnam(name).pw_dir - except (ImportError, KeyError): + except ImportError: # pwd module unavailable, return path unchanged + return path + name = path[1:i] + if isinstance(name, bytes): + name = name.decode('ascii') + try: + userhome = pwd.getpwnam(name).pw_dir + except KeyError: # bpo-10496: if the user name from the path doesn't exist in the # password database, return the path unchanged return path From 6f8e7d781e64e10cb1627a64c8c482d8f33502fd Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Sat, 30 Mar 2024 17:44:36 +0100 Subject: [PATCH 32/46] Update Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst Co-authored-by: Alex Waygood --- .../2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst index c46ff82dcd6d23..7a7bc689002017 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst @@ -1 +1 @@ -Hardcode constants in :mod:`ntpath`. +Optimise several functions in :mod:`os.path`. From 47a4b57c4a21cd2beae886cd4fd8c8f98bdcf2c4 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 17:54:05 +0100 Subject: [PATCH 33/46] Update ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 03e458d170d2b8..fe014a364dd82d 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -191,6 +191,7 @@ Finn Bock Paul Boddie Matthew Boedicker Robin Boerdijk +Wannes Boeykens Andra Bogildea Matt Bogosian Nikolay Bogoychev From 48f29712efba695a21e12445da276b5d7d35f933 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 18:34:13 +0100 Subject: [PATCH 34/46] Remove `_get_sep()` call Co-Authored-By: Barney Gale --- Lib/posixpath.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index cd4c17453d95b4..3c1b148ff0b764 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -374,13 +374,12 @@ def normpath(path): def abspath(path): """Return an absolute path.""" path = os.fspath(path) - sep = _get_sep(path) - if path[:1] != sep: - if isinstance(path, bytes): - cwd = os.getcwdb() - else: - cwd = os.getcwd() - path = join(cwd, path) + if isinstance(path, bytes): + if path[:1] != b'/': + path = join(os.getcwdb(), path) + else: + if path[:1] != '/': + path = join(os.getcwd(), path) return normpath(path) From 33259d98d975d19d1b6e07fb64b3e9e000194ae9 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 18:41:56 +0100 Subject: [PATCH 35/46] Revert unnecessary change --- Lib/ntpath.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index b42877ba64b17b..e14afb612dd29c 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -865,7 +865,8 @@ def commonpath(paths): raise ValueError("Paths don't have the same drive") drive, root, path = splitroot(paths[0].replace(altsep, sep)) - common = [c for c in path.split(sep) if c and c != curdir] + common = path.split(sep) + common = [c for c in common if c and c != curdir] split_paths = [ [c for c in p.split(sep) if c and c != curdir] From 4568cd193f04ecf2306fad33ffc25dca64aa475e Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 19:05:54 +0100 Subject: [PATCH 36/46] Revert insignificant changes --- Lib/ntpath.py | 8 ++++++-- Lib/posixpath.py | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index e14afb612dd29c..ba722ae8b0f756 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -876,8 +876,12 @@ def commonpath(paths): s2 = max(split_paths) for i, c in enumerate(s1): if c != s2[i]: - return drive + root + sep.join(common[:i]) - return drive + root + sep.join(common[:len(s1)]) + common = common[:i] + break + else: + common = common[:len(s1)] + + return drive + root + sep.join(common) except (TypeError, AttributeError): genericpath._check_arg_types('commonpath', *paths) raise diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 3c1b148ff0b764..82347d250431ff 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -117,9 +117,12 @@ def split(p): def splitext(p): p = os.fspath(p) if isinstance(p, bytes): - return genericpath._splitext(p, b'/', None, b'.') + sep = b'/' + extsep = b'.' else: - return genericpath._splitext(p, '/', None, '.') + sep = '/' + extsep = '.' + return genericpath._splitext(p, sep, None, extsep) splitext.__doc__ = genericpath._splitext.__doc__ # Split a pathname into a drive specification and the rest of the @@ -240,11 +243,12 @@ def expanduser(path): # pwd module unavailable, return path unchanged return path try: - userhome = pwd.getpwuid(os.getuid()).pw_dir + pwent = pwd.getpwnam(name) except KeyError: # bpo-10496: if the current user identifier doesn't exist in the # password database, return the path unchanged return path + userhome = pwent.pw_dir else: userhome = os.environ['HOME'] else: @@ -352,8 +356,9 @@ def normpath(path): if not path: return dot _, initial_slashes, path = splitroot(path) + comps = path.split(sep) new_comps = [] - for comp in path.split(sep): + for comp in comps: if not comp or comp == dot: continue if (comp != dotdot or (not initial_slashes and not new_comps) or @@ -361,7 +366,9 @@ def normpath(path): new_comps.append(comp) elif new_comps: new_comps.pop() - return (initial_slashes + sep.join(new_comps)) or dot + comps = new_comps + path = initial_slashes + sep.join(comps) + return path or dot else: def normpath(path): """Normalize path, eliminating double slashes, etc.""" From 1382f49da0bf7bcf962a27364688083b56c2c746 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 19:08:36 +0100 Subject: [PATCH 37/46] Fix incorrect change --- Lib/posixpath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 82347d250431ff..b246c6363c993e 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -243,12 +243,11 @@ def expanduser(path): # pwd module unavailable, return path unchanged return path try: - pwent = pwd.getpwnam(name) + userhome = pwd.getpwuid(os.getuid()).pw_dir except KeyError: # bpo-10496: if the current user identifier doesn't exist in the # password database, return the path unchanged return path - userhome = pwent.pw_dir else: userhome = os.environ['HOME'] else: @@ -261,11 +260,12 @@ def expanduser(path): if isinstance(name, bytes): name = name.decode('ascii') try: - userhome = pwd.getpwnam(name).pw_dir + pwent = pwd.getpwnam(name) except KeyError: # bpo-10496: if the user name from the path doesn't exist in the # password database, return the path unchanged return path + userhome = pwent.pw_dir # if no user home, return the path unchanged on VxWorks if userhome is None and sys.platform == "vxworks": return path From a81dc6e349c592c45a46d4c2540b4e3285ffbd92 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 20:08:50 +0100 Subject: [PATCH 38/46] Revert unnesting --- Lib/posixpath.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index b246c6363c993e..5ab0767c45cb70 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -369,6 +369,7 @@ def normpath(path): comps = new_comps path = initial_slashes + sep.join(comps) return path or dot + else: def normpath(path): """Normalize path, eliminating double slashes, etc.""" @@ -424,15 +425,16 @@ def _joinrealpath(path, rest, strict, seen): continue if name == pardir: # parent dir - if not path: + if path: + if basename(path) == pardir: + # ../.. + path = join(path, pardir) + else: + # foo/bar/.. -> foo + path = dirname(path) + else: # .. path = pardir - elif basename(path) == pardir: - # ../.. - path = join(path, pardir) - else: - # foo/bar/.. -> foo - path = dirname(path) continue newpath = join(path, name) try: From e6cb4a326a678f10940b6d0a13e41cec57fadea4 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 20:31:12 +0100 Subject: [PATCH 39/46] Move to new pull request --- Lib/posixpath.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 5ab0767c45cb70..0e9c5efbca6127 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -202,7 +202,12 @@ def ismount(path): if stat.S_ISLNK(s1.st_mode): return False - parent = dirname(abspath(path)) + path = os.fspath(path) + if isinstance(path, bytes): + parent = join(path, b'..') + else: + parent = join(path, '..') + parent = realpath(parent) try: s2 = os.lstat(parent) except (OSError, ValueError): From 67ec9a619f312a23f36e69755f9ceae02c7cc326 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sat, 30 Mar 2024 21:04:55 +0100 Subject: [PATCH 40/46] assign `devnull` first --- Lib/ntpath.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index ba722ae8b0f756..83b2a961a4857a 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -722,7 +722,8 @@ def realpath(path, *, strict=False): new_unc_prefix = b'\\\\' cwd = os.getcwdb() # bpo-38081: Special case for realpath(b'nul') - if normcase(path) == b'nul': + devnull = b'nul' + if normcase(path) == devnull: return b'\\\\.\\NUL' else: prefix = '\\\\?\\' @@ -730,7 +731,8 @@ def realpath(path, *, strict=False): new_unc_prefix = '\\\\' cwd = os.getcwd() # bpo-38081: Special case for realpath('nul') - if normcase(path) == 'nul': + devnull = 'nul' + if normcase(path) == devnull: return '\\\\.\\NUL' had_prefix = path[:4] == prefix if not had_prefix and not isabs(path): From 87d43b875962811cc661d13b2c18c3543eb63aea Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sun, 31 Mar 2024 21:36:12 +0200 Subject: [PATCH 41/46] speedup `posixpath.realpath` --- Lib/posixpath.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 0e9c5efbca6127..2710832d7f20f6 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -431,12 +431,13 @@ def _joinrealpath(path, rest, strict, seen): if name == pardir: # parent dir if path: - if basename(path) == pardir: + parent, name = split(path) + if name == pardir: # ../.. path = join(path, pardir) else: # foo/bar/.. -> foo - path = dirname(path) + path = parent else: # .. path = pardir From 6c8901ac73d490ff0bfc0c2fe8f6b4e49464608c Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Mon, 1 Apr 2024 17:07:18 +0200 Subject: [PATCH 42/46] Speedup `ntpath.isreserved()` --- Lib/ntpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 83b2a961a4857a..1e40c2b223ac01 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -340,8 +340,8 @@ def isreserved(path): def _isreservedname(name): """Return true if the filename is reserved by the system.""" # Trailing dots and spaces are reserved. - if name[-1:] in ('.', ' ') and name not in ('.', '..'): - return True + if name[-1:] in ('.', ' '): + return name not in ('.', '..') # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved. # ASCII control characters (0-31) are reserved. # Colon is reserved for file streams (e.g. "name:stream[:type]"). From 7940a6414df7b012be20c28b711487fbeb703426 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Mon, 1 Apr 2024 19:02:56 +0200 Subject: [PATCH 43/46] Revert starts- & endswith --- Lib/ntpath.py | 14 ++++++-------- Lib/posixpath.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 1e40c2b223ac01..aa65ee2b01ee11 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -371,7 +371,7 @@ def expanduser(path): tilde = b'~' else: tilde = '~' - if path[:1] != tilde: + if not path.startswith(tilde): return path i, n = 1, len(path) while i < n and path[i] not in _get_bothseps(path): @@ -734,7 +734,7 @@ def realpath(path, *, strict=False): devnull = 'nul' if normcase(path) == devnull: return '\\\\.\\NUL' - had_prefix = path[:4] == prefix + had_prefix = path.startswith(prefix) if not had_prefix and not isabs(path): path = join(cwd, path) try: @@ -756,10 +756,10 @@ def realpath(path, *, strict=False): # The path returned by _getfinalpathname will always start with \\?\ - # strip off that prefix unless it was already provided on the original # path. - if not had_prefix and path[:4] == prefix: + if not had_prefix and path.startswith(prefix): # For UNC paths, the prefix will actually be \\?\UNC\ # Handle that case as well. - if path[:8] == unc_prefix: + if path.startswith(unc_prefix): spath = new_unc_prefix + path[8:] else: spath = path[4:] @@ -856,6 +856,7 @@ def commonpath(paths): try: drivesplits = [splitroot(p.replace(altsep, sep).lower()) for p in paths] + split_paths = [p.split(sep) for d, r, p in drivesplits] if len({r for d, r, p in drivesplits}) != 1: raise ValueError("Can't mix absolute and relative paths") @@ -870,10 +871,7 @@ def commonpath(paths): common = path.split(sep) common = [c for c in common if c and c != curdir] - split_paths = [ - [c for c in p.split(sep) if c and c != curdir] - for d, r, p in drivesplits - ] + split_paths = [[c for c in s if c and c != curdir] for s in split_paths] s1 = min(split_paths) s2 = max(split_paths) for i, c in enumerate(s1): diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 2710832d7f20f6..76ee721bfb5e33 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -61,7 +61,7 @@ def isabs(s): """Test whether a path is absolute""" s = os.fspath(s) sep = _get_sep(s) - return s[:1] == sep + return s.startswith(sep) # Join pathnames. @@ -80,9 +80,9 @@ def join(a, *p): if not p: path[:0] + sep #23780: Ensure compatible data type even if p is null. for b in map(os.fspath, p): - if b[:1] == sep: + if b.startswith(sep): path = b - elif not path or path[-1:] == sep: + elif not path or path.endswith(sep): path += b else: path += sep + b @@ -234,7 +234,7 @@ def expanduser(path): tilde = b'~' else: tilde = '~' - if path[:1] != tilde: + if not path.startswith(tilde): return path sep = _get_sep(path) i = path.find(sep, 1) @@ -322,7 +322,7 @@ def expandvars(path): break i, j = m.span(0) name = m.group(1) - if name[:1] == start and name[-1:] == end: + if name.startswith(start) and name.endswith(end): name = name[1:-1] try: if environ is None: @@ -388,10 +388,10 @@ def abspath(path): """Return an absolute path.""" path = os.fspath(path) if isinstance(path, bytes): - if path[:1] != b'/': + if not path.startswith(b'/'): path = join(os.getcwdb(), path) else: - if path[:1] != '/': + if not path.startswith('/'): path = join(os.getcwd(), path) return normpath(path) @@ -419,7 +419,7 @@ def _joinrealpath(path, rest, strict, seen): curdir = '.' pardir = '..' - if rest[:1] == sep: + if rest.startswith(sep): rest = rest[1:] path = sep From e637698344b2ae75f821480cba9dbf347af1aaf0 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Mon, 1 Apr 2024 20:02:54 +0200 Subject: [PATCH 44/46] Revert `len()` call --- Lib/ntpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index aa65ee2b01ee11..476028e79a04bb 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -760,9 +760,9 @@ def realpath(path, *, strict=False): # For UNC paths, the prefix will actually be \\?\UNC\ # Handle that case as well. if path.startswith(unc_prefix): - spath = new_unc_prefix + path[8:] + spath = new_unc_prefix + path[len(unc_prefix):] else: - spath = path[4:] + spath = path[len(prefix):] # Ensure that the non-prefixed path resolves to the same path try: if _getfinalpathname(spath) == path: From b5fdd278fa235860e46a794746f02362f12ed48b Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Mon, 1 Apr 2024 20:28:28 +0200 Subject: [PATCH 45/46] ;( --- Lib/ntpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 476028e79a04bb..bdf821807ca782 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -334,8 +334,8 @@ def isreserved(path): """Return true if the pathname is reserved by the system.""" # Refer to "Naming Files, Paths, and Namespaces": # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file - path = os.fsdecode(splitroot(path)[2]).replace('/', '\\') - return any(_isreservedname(name) for name in reversed(path.split('\\'))) + path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep) + return any(_isreservedname(name) for name in reversed(path.split(sep))) def _isreservedname(name): """Return true if the filename is reserved by the system.""" From 887f3c1941b33006c35fe1421f8ce31e61cf808e Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Tue, 2 Apr 2024 21:46:35 +0200 Subject: [PATCH 46/46] Replace definition of colon Co-authored-by: Pieter Eendebak --- Lib/ntpath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index bdf821807ca782..b4319be0420149 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -102,11 +102,11 @@ def join(path, *paths): if isinstance(path, bytes): sep = b'\\' seps = b'\\/' - colon = b':' + colon_seps = b':\\/' else: sep = '\\' seps = '\\/' - colon = ':' + colon_seps = ':\\/' try: if not paths: path[:0] + sep #23780: Ensure compatible data type even if p is null. @@ -135,7 +135,7 @@ def join(path, *paths): result_path = result_path + p_path ## add separator between UNC and non-absolute path if (result_path and not result_root and - result_drive and result_drive[-1] not in colon + seps): + result_drive and result_drive[-1] not in colon_seps): return result_drive + sep + result_path return result_drive + result_root + result_path except (TypeError, AttributeError, BytesWarning):