diff -r 87b81b7df7f0 Lib/pathlib.py --- a/Lib/pathlib.py Mon Dec 16 20:22:37 2013 +0100 +++ b/Lib/pathlib.py Mon Dec 16 21:44:26 2013 +0200 @@ -8,7 +8,7 @@ import sys from collections import Sequence from contextlib import contextmanager -from errno import EINVAL, ENOENT +from errno import EINVAL from operator import attrgetter from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO from urllib.parse import quote_from_bytes as urlquote_from_bytes @@ -42,7 +42,7 @@ return "*" in pat or "?" in pat or "[" in pat -class _Flavour(object): +class _Flavour: """A flavour implements a particular (platform-specific) set of path semantics.""" @@ -116,7 +116,6 @@ set(chr(x) for x in range(ord('a'), ord('z') + 1)) | set(chr(x) for x in range(ord('A'), ord('Z') + 1)) ) - ext_namespace_prefix = '\\\\?\\' reserved_names = ( {'CON', 'PRN', 'AUX', 'NUL'} | @@ -184,15 +183,12 @@ # Means fallback on absolute return None - def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): - prefix = '' - if s.startswith(ext_prefix): - prefix = s[:4] - s = s[4:] - if s.startswith('UNC\\'): - prefix += s[:3] - s = '\\' + s[3:] - return prefix, s + def _split_extended_path(self, s): + if s[:4] == '\\\\?\\': + if s[4:8] == 'UNC\\': + return '\\\\?\\UNC', '\\\\' + s[8:] + return '\\\\?\\', s[4:] + return '', s def _ext_to_normal(self, s): # Turn back an extended path into a normal DOS-like path @@ -205,7 +201,7 @@ # not considered reserved by Windows. if not parts: return False - if parts[0].startswith('\\\\'): + if parts[0][:2] == '\\\\': # UNC paths are never reserved return False return parts[-1].partition('.')[0].upper() in self.reserved_names @@ -529,7 +525,7 @@ return len(self._parts) def __getitem__(self, idx): - if idx < 0 or idx >= len(self): + if not (0 <= idx < len(self)): raise IndexError(idx) return self._pathcls._from_parsed_parts(self._drv, self._root, self._parts[:-idx - 1]) @@ -607,7 +603,7 @@ @classmethod def _format_parsed_parts(cls, drv, root, parts): - if drv or root: + if root or drv: return drv + root + cls._flavour.join(parts[1:]) else: return cls._flavour.join(parts) @@ -635,8 +631,7 @@ def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" - f = self._flavour - return str(self).replace(f.sep, '/') + return str(self).replace(self._flavour.sep, '/') def __bytes__(self): """Return the bytes representation of the path. This is only @@ -705,14 +700,15 @@ @property def anchor(self): """The concatenation of the drive and root, or ''.""" - anchor = self._drv + self._root - return anchor + return self._drv + self._root @property def name(self): """The final path component, if any.""" parts = self._parts - if len(parts) == (1 if (self._drv or self._root) else 0): + if not parts: + return '' + if (self._root or self._drv) and len(parts) == 1: return '' return parts[-1] @@ -730,7 +726,7 @@ def suffixes(self): """A list of the final component's suffixes, if any.""" name = self.name - if name.endswith('.'): + if not name or name[-1] == '.': return [] name = name.lstrip('.') return ['.' + suffix for suffix in name.split('.')[1:]] @@ -780,23 +776,17 @@ parts = self._parts drv = self._drv root = self._root - if drv or root: - if root: - abs_parts = [drv, root] + parts[1:] - else: - abs_parts = [drv] + parts[1:] + if root: + abs_parts = [drv, root] + parts[1:] else: abs_parts = parts to_drv, to_root, to_parts = self._parse_args(other) - if to_drv or to_root: - if to_root: - to_abs_parts = [to_drv, to_root] + to_parts[1:] - else: - to_abs_parts = [to_drv] + to_parts[1:] + if to_root: + to_abs_parts = [to_drv, to_root] + to_parts[1:] else: to_abs_parts = to_parts n = len(to_abs_parts) - if n == 0 and (drv or root) or abs_parts[:n] != to_abs_parts: + if not n and (root or drv) or abs_parts[:n] != to_abs_parts: formatted = self._format_parsed_parts(to_drv, to_root, to_parts) raise ValueError("{!r} does not start with {!r}" .format(str(self), str(formatted))) @@ -834,7 +824,7 @@ drv = self._drv root = self._root parts = self._parts - if len(parts) == 1 and (drv or root): + if (root or drv) and len(parts) == 1: return self return self._from_parsed_parts(drv, root, parts[:-1]) @@ -869,14 +859,15 @@ if root and root != cf(self._root): return False parts = self._cparts - if drv or root: + if root or drv: if len(pat_parts) != len(parts): return False pat_parts = pat_parts[1:] elif len(pat_parts) > len(parts): return False + match = fnmatch.fnmatchcase for part, pat in zip(reversed(parts), reversed(pat_parts)): - if not fnmatch.fnmatchcase(part, pat): + if not match(part, pat): return False return True @@ -979,11 +970,10 @@ """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) - if drv or root: + if root or drv: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(tuple(pattern_parts)) - for p in selector.select_from(self): - yield p + yield from selector.select_from(self) def rglob(self, pattern): """Recursively yield all existing files (of any kind, including @@ -991,11 +981,10 @@ """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) - if drv or root: + if root or drv: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(("**",) + tuple(pattern_parts)) - for p in selector.select_from(self): - yield p + yield from selector.select_from(self) def absolute(self): """Return an absolute version of this path. This function works @@ -1098,9 +1087,7 @@ else: try: self._accessor.mkdir(self, mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: self.parent.mkdir(parents=True) self._accessor.mkdir(self, mode) @@ -1181,9 +1168,7 @@ """ try: self.stat() - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: return False return True @@ -1193,9 +1178,7 @@ """ try: return S_ISDIR(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False @@ -1207,9 +1190,7 @@ """ try: return S_ISREG(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False @@ -1220,9 +1201,7 @@ """ try: return S_ISLNK(self.lstat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist return False @@ -1232,9 +1211,7 @@ """ try: return S_ISBLK(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False @@ -1245,9 +1222,7 @@ """ try: return S_ISCHR(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False @@ -1258,9 +1233,7 @@ """ try: return S_ISFIFO(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False @@ -1271,9 +1244,7 @@ """ try: return S_ISSOCK(self.stat().st_mode) - except OSError as e: - if e.errno != ENOENT: - raise + except FileNotFoundError: # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False diff -r 87b81b7df7f0 Lib/test/test_pathlib.py --- a/Lib/test/test_pathlib.py Mon Dec 16 20:22:37 2013 +0100 +++ b/Lib/test/test_pathlib.py Mon Dec 16 21:44:26 2013 +0200 @@ -7,10 +7,8 @@ import shutil import socket import stat -import sys import tempfile import unittest -from contextlib import contextmanager from test import support TESTFN = support.TESTFN @@ -1143,24 +1141,15 @@ # This one goes upwards but doesn't create a loop self.dirlink(os.path.join('..', 'dirB'), join('dirB', 'linkD')) - if os.name == 'nt': - # Workaround for http://bugs.python.org/issue13772 - def dirlink(self, src, dest): - os.symlink(src, dest, target_is_directory=True) - else: - def dirlink(self, src, dest): - os.symlink(src, dest) + # Workaround for http://bugs.python.org/issue13772 + def dirlink(self, src, dest): + os.symlink(src, dest, target_is_directory=True) def assertSame(self, path_a, path_b): self.assertTrue(os.path.samefile(str(path_a), str(path_b)), "%r and %r don't point to the same file" % (path_a, path_b)) - def assertFileNotFound(self, func, *args, **kwargs): - with self.assertRaises(FileNotFoundError) as cm: - func(*args, **kwargs) - self.assertEqual(cm.exception.errno, errno.ENOENT) - def _test_cwd(self, p): q = self.cls(os.getcwd()) self.assertEqual(p, q) @@ -1292,9 +1281,8 @@ def test_resolve_common(self): P = self.cls p = P(BASE, 'foo') - with self.assertRaises(OSError) as cm: + with self.assertRaises(FileNotFoundError) as cm: p.resolve() - self.assertEqual(cm.exception.errno, errno.ENOENT) # These are all relative symlinks p = P(BASE, 'dirB', 'fileB') self._check_resolve_relative(p, p) @@ -1396,16 +1384,16 @@ def test_unlink(self): p = self.cls(BASE) / 'fileA' p.unlink() - self.assertFileNotFound(p.stat) - self.assertFileNotFound(p.unlink) + self.assertRaises(FileNotFoundError, p.stat) + self.assertRaises(FileNotFoundError, p.unlink) def test_rmdir(self): p = self.cls(BASE) / 'dirA' for q in p.iterdir(): q.unlink() p.rmdir() - self.assertFileNotFound(p.stat) - self.assertFileNotFound(p.unlink) + self.assertRaises(FileNotFoundError, p.stat) + self.assertRaises(FileNotFoundError, p.unlink) def test_rename(self): P = self.cls(BASE) @@ -1415,12 +1403,12 @@ q = P / 'dirA' / 'fileAA' p.rename(q) self.assertEqual(q.stat().st_size, size) - self.assertFileNotFound(p.stat) + self.assertRaises(FileNotFoundError, p.stat) # Renaming to a str of a relative path r = rel_join('fileAAA') q.rename(r) self.assertEqual(os.stat(r).st_size, size) - self.assertFileNotFound(q.stat) + self.assertRaises(FileNotFoundError, q.stat) def test_replace(self): P = self.cls(BASE) @@ -1430,12 +1418,12 @@ q = P / 'dirA' / 'fileAA' p.replace(q) self.assertEqual(q.stat().st_size, size) - self.assertFileNotFound(p.stat) + self.assertRaises(FileNotFoundError, p.stat) # Replacing another (existing) path r = rel_join('dirB', 'fileB') q.replace(r) self.assertEqual(os.stat(r).st_size, size) - self.assertFileNotFound(q.stat) + self.assertRaises(FileNotFoundError, q.stat) def test_touch_common(self): P = self.cls(BASE) @@ -1475,7 +1463,7 @@ p.mkdir() self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) - with self.assertRaises(OSError) as cm: + with self.assertRaises(FileExistsError) as cm: p.mkdir() self.assertEqual(cm.exception.errno, errno.EEXIST) @@ -1483,13 +1471,12 @@ # Creating a chain of directories p = self.cls(BASE, 'newdirB', 'newdirC') self.assertFalse(p.exists()) - with self.assertRaises(OSError) as cm: + with self.assertRaises(FileNotFoundError) as cm: p.mkdir() - self.assertEqual(cm.exception.errno, errno.ENOENT) p.mkdir(parents=True) self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) - with self.assertRaises(OSError) as cm: + with self.assertRaises(FileExistsError) as cm: p.mkdir(parents=True) self.assertEqual(cm.exception.errno, errno.EEXIST) # test `mode` arg