From e503cd62fd61b594f8a60c244559c4f6d8159b43 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 28 Dec 2019 02:17:28 +0800 Subject: [PATCH 1/5] Prepend cwd if resolve can't find existing prefix This guarentees we are returning an absolute path when the input `path` is relative. Nothing would change if `path` is already absolute. --- Lib/pathlib.py | 4 ++-- Lib/test/test_pathlib.py | 10 ++++++++++ .../Library/2019-12-28-02-50-12.bpo-38671.xT4R0P.rst | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-12-28-02-50-12.bpo-38671.xT4R0P.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 37934c6038e1d1..c3b5aa887b6805 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -202,9 +202,9 @@ def resolve(self, path, strict=False): except FileNotFoundError: previous_s = s s, tail = os.path.split(s) - tail_parts.append(tail) if previous_s == s: - return path + return os.path.join(s or os.getcwd(), *reversed(tail_parts)) + tail_parts.append(tail) else: return os.path.join(s, *reversed(tail_parts)) # Means fallback on absolute diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 6ed08f7e70ce3d..0cda617269b9a7 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2654,6 +2654,16 @@ def check(): env['HOME'] = 'C:\\Users\\eve' check() + def test_resolve_nonexist_relative_issue38671(self): + p = self.cls('non', 'exist') + + old_cwd = os.getcwd() + os.chdir(BASE) + try: + self.assertEqual(p.resolve(), self.cls(BASE, p)) + finally: + os.chdir(old_cwd) + class CompatiblePathTest(unittest.TestCase): """ diff --git a/Misc/NEWS.d/next/Library/2019-12-28-02-50-12.bpo-38671.xT4R0P.rst b/Misc/NEWS.d/next/Library/2019-12-28-02-50-12.bpo-38671.xT4R0P.rst new file mode 100644 index 00000000000000..a012fa57c3d976 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-12-28-02-50-12.bpo-38671.xT4R0P.rst @@ -0,0 +1,2 @@ +Ensure pathlib.Path.resolve(strict=False) returns an absolute path on +Windows when the path is relative and does not exist. From 3f9d1b8e70f3bf8f4f19d4107b3c8a20dc0d3b21 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Wed, 15 Jul 2020 21:35:38 +0800 Subject: [PATCH 2/5] Use os.path.abspath() as fallback as suggested --- Lib/pathlib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index c3b5aa887b6805..565ab15fdf948c 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -195,6 +195,7 @@ def resolve(self, path, strict=False): if strict: return self._ext_to_normal(_getfinalpathname(s)) else: + s = path = os.path.abspath(s) tail_parts = [] # End of the path after the first one not found while True: try: @@ -202,9 +203,9 @@ def resolve(self, path, strict=False): except FileNotFoundError: previous_s = s s, tail = os.path.split(s) - if previous_s == s: - return os.path.join(s or os.getcwd(), *reversed(tail_parts)) tail_parts.append(tail) + if previous_s == s: + return path else: return os.path.join(s, *reversed(tail_parts)) # Means fallback on absolute From 612b7f6c4a8f7175a7d3d85842408be3bc92ad0a Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Wed, 15 Jul 2020 21:38:13 +0800 Subject: [PATCH 3/5] Use early returns to reduce indentation level --- Lib/pathlib.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 565ab15fdf948c..1fa8f7c50958ae 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -189,27 +189,25 @@ def compile_pattern(self, pattern): def resolve(self, path, strict=False): s = str(path) if not s: - return path._accessor.getcwd() + s = path._accessor.getcwd() + if _getfinalpathname is None: + return None # Means fallback on absolute + if strict: + return self._ext_to_normal(_getfinalpathname(s)) + s = path = os.path.abspath(s) previous_s = None - if _getfinalpathname is not None: - if strict: - return self._ext_to_normal(_getfinalpathname(s)) + tail_parts = [] # End of the path after the first one not found + while True: + try: + s = self._ext_to_normal(_getfinalpathname(s)) + except FileNotFoundError: + previous_s = s + s, tail = os.path.split(s) + tail_parts.append(tail) + if previous_s == s: # Root reached, fallback to abspath() + return path else: - s = path = os.path.abspath(s) - tail_parts = [] # End of the path after the first one not found - while True: - try: - s = self._ext_to_normal(_getfinalpathname(s)) - except FileNotFoundError: - previous_s = s - s, tail = os.path.split(s) - tail_parts.append(tail) - if previous_s == s: - return path - else: - return os.path.join(s, *reversed(tail_parts)) - # Means fallback on absolute - return None + return os.path.join(s, *reversed(tail_parts)) def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): prefix = '' From 7cff82ea1b9b44310343fa617596a1c6426dd553 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 19 Jul 2020 22:27:48 +0800 Subject: [PATCH 4/5] Test non-exist resolve on all platforms --- Lib/test/test_pathlib.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 0cda617269b9a7..4fbd46f4923c88 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1799,6 +1799,16 @@ def test_resolve_dot(self): # Non-strict self.assertEqual(r.resolve(strict=False), p / '3' / '4') + def test_resolve_nonexist_relative_issue38671(self): + p = self.cls('non', 'exist') + + old_cwd = os.getcwd() + os.chdir(BASE) + try: + self.assertEqual(p.resolve(), self.cls(BASE, p)) + finally: + os.chdir(old_cwd) + def test_with(self): p = self.cls(BASE) it = p.iterdir() @@ -2654,16 +2664,6 @@ def check(): env['HOME'] = 'C:\\Users\\eve' check() - def test_resolve_nonexist_relative_issue38671(self): - p = self.cls('non', 'exist') - - old_cwd = os.getcwd() - os.chdir(BASE) - try: - self.assertEqual(p.resolve(), self.cls(BASE, p)) - finally: - os.chdir(old_cwd) - class CompatiblePathTest(unittest.TestCase): """ From 8f20c10011e42757a881e1bbdd8f929ef144b458 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 19 Jul 2020 22:33:38 +0800 Subject: [PATCH 5/5] Use nt._getfullpathname instead of os.path.abspath --- Lib/pathlib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 1fa8f7c50958ae..1cb29a7d325110 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -15,7 +15,7 @@ if os.name == 'nt': - from nt import _getfinalpathname + from nt import _getfinalpathname, _getfullpathname else: _getfinalpathname = None @@ -194,7 +194,7 @@ def resolve(self, path, strict=False): return None # Means fallback on absolute if strict: return self._ext_to_normal(_getfinalpathname(s)) - s = path = os.path.abspath(s) + s = path = _getfullpathname(s) previous_s = None tail_parts = [] # End of the path after the first one not found while True: @@ -204,7 +204,8 @@ def resolve(self, path, strict=False): previous_s = s s, tail = os.path.split(s) tail_parts.append(tail) - if previous_s == s: # Root reached, fallback to abspath() + if previous_s == s: + # Root reached, fallback to _getfullpathname() return path else: return os.path.join(s, *reversed(tail_parts))