Skip to content

Commit

Permalink
Sync with upstream pathlib. (#51)
Browse files Browse the repository at this point in the history
This includes a fix for issue #47.
  • Loading branch information
mcmtroffaes committed Mar 11, 2019
1 parent 510baa5 commit f9af3d0
Showing 1 changed file with 107 additions and 15 deletions.
122 changes: 107 additions & 15 deletions pathlib2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import six
import sys
from collections import Sequence
from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES
from errno import EINVAL, ENOENT, ENOTDIR, EBADF
from errno import EEXIST, EPERM, EACCES
from operator import attrgetter

from stat import (
S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO)
try:
Expand Down Expand Up @@ -54,6 +54,18 @@
# Internals
#

# EBADF - guard agains macOS `stat` throwing EBADF
_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF)

_IGNORED_WINERRORS = (
21, # ERROR_NOT_READY - drive exists but is not accessible
)


def _ignore_error(exception):
return (getattr(exception, 'errno', None) in _IGNORED_ERROS or
getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)


def _py2_fsencode(parts):
# py2 => minimal unicode support
Expand Down Expand Up @@ -715,7 +727,13 @@ def _iterate_directories(self, parent_path, is_dir, scandir):
def try_iter():
entries = list(scandir(parent_path))
for entry in entries:
if entry.is_dir() and not entry.is_symlink():
entry_is_dir = False
try:
entry_is_dir = entry.is_dir()
except OSError as e:
if not _ignore_error(e):
raise
if entry_is_dir and not entry.is_symlink():
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, is_dir, scandir):
yield p
Expand Down Expand Up @@ -1026,8 +1044,9 @@ def with_name(self, name):
self._parts[:-1] + [name])

def with_suffix(self, suffix):
"""Return a new path with the file suffix changed (or added, if
none).
"""Return a new path with the file suffix changed. If the path
has no suffix, add given suffix. If the given suffix is an empty
string, remove the suffix from the path.
"""
# XXX if suffix is None, should the current suffix be removed?
f = self._flavour
Expand Down Expand Up @@ -1173,6 +1192,11 @@ class PurePosixPath(PurePath):


class PureWindowsPath(PurePath):
"""PurePath subclass for Windows systems.
On a Windows system, instantiating a PurePath should return this object.
However, you can also instantiate it directly on any system.
"""
_flavour = _windows_flavour
__slots__ = ()

Expand All @@ -1181,6 +1205,14 @@ class PureWindowsPath(PurePath):


class Path(PurePath):
"""PurePath subclass that can make system calls.
Path represents a filesystem path but unlike PurePath, also offers
methods to do system calls on path objects. Depending on your system,
instantiating a Path will return either a PosixPath or a WindowsPath
object. You can also instantiate a PosixPath or WindowsPath directly,
but cannot instantiate a WindowsPath on a POSIX system or vice versa.
"""
__slots__ = (
'_accessor',
'_closed',
Expand Down Expand Up @@ -1286,7 +1318,7 @@ def iterdir(self):

def glob(self, pattern):
"""Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given pattern.
kind, including directories) matching the given relative pattern.
"""
if not pattern:
raise ValueError("Unacceptable pattern: {0!r}".format(pattern))
Expand All @@ -1300,7 +1332,8 @@ def glob(self, pattern):

def rglob(self, pattern):
"""Recursively yield all existing files (of any kind, including
directories) matching the given pattern, anywhere in this subtree.
directories) matching the given relative pattern, anywhere in
this subtree.
"""
pattern = self._flavour.casefold(pattern)
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
Expand Down Expand Up @@ -1463,6 +1496,8 @@ def _exc_func(exc):
try:
_try_except_filenotfounderror(_try_func, _exc_func)
except OSError:
# Cannot rely on checking for EEXIST, since the operating system
# could give priority to other errors like EACCES or EROFS
if not exist_ok or not self.is_dir():
raise

Expand Down Expand Up @@ -1548,9 +1583,12 @@ def exists(self):
try:
self.stat()
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
return False
except ValueError:
# Non-encodable path
return False
return True

def is_dir(self):
Expand All @@ -1560,11 +1598,14 @@ def is_dir(self):
try:
return S_ISDIR(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def is_file(self):
"""
Expand All @@ -1574,11 +1615,35 @@ def is_file(self):
try:
return S_ISREG(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def is_mount(self):
"""
Check if this path is a POSIX mount point
"""
# Need to exist and be a dir
if not self.exists() or not self.is_dir():
return False

parent = Path(self.parent)
try:
parent_dev = parent.stat().st_dev
except OSError:
return False

dev = self.stat().st_dev
if dev != parent_dev:
return True
ino = self.stat().st_ino
parent_ino = parent.stat().st_ino
return ino == parent_ino

def is_symlink(self):
"""
Expand All @@ -1587,10 +1652,13 @@ def is_symlink(self):
try:
return S_ISLNK(self.lstat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist
return False
except ValueError:
# Non-encodable path
return False

def is_block_device(self):
"""
Expand All @@ -1599,11 +1667,14 @@ def is_block_device(self):
try:
return S_ISBLK(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def is_char_device(self):
"""
Expand All @@ -1612,11 +1683,14 @@ def is_char_device(self):
try:
return S_ISCHR(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def is_fifo(self):
"""
Expand All @@ -1625,11 +1699,14 @@ def is_fifo(self):
try:
return S_ISFIFO(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def is_socket(self):
"""
Expand All @@ -1638,11 +1715,14 @@ def is_socket(self):
try:
return S_ISSOCK(self.stat().st_mode)
except OSError as e:
if e.errno not in (ENOENT, ENOTDIR):
if not _ignore_error(e):
raise
# Path doesn't exist or is a broken symlink
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False
except ValueError:
# Non-encodable path
return False

def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
Expand All @@ -1657,14 +1737,26 @@ def expanduser(self):


class PosixPath(Path, PurePosixPath):
"""Path subclass for non-Windows systems.
On a POSIX system, instantiating a Path should return this object.
"""
__slots__ = ()


class WindowsPath(Path, PureWindowsPath):
"""Path subclass for Windows systems.
On a Windows system, instantiating a Path should return this object.
"""
__slots__ = ()

def owner(self):
raise NotImplementedError("Path.owner() is unsupported on this system")

def group(self):
raise NotImplementedError("Path.group() is unsupported on this system")

def is_mount(self):
raise NotImplementedError(
"Path.is_mount() is unsupported on this system")

0 comments on commit f9af3d0

Please sign in to comment.