Skip to content

Commit

Permalink
Patch is_reserved for Windows/PosixPurePath
Browse files Browse the repository at this point in the history
- had been only patched for pathlib.Path
  • Loading branch information
mrbean-bremen committed Oct 5, 2024
1 parent 7ac7064 commit 2325e39
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 34 deletions.
9 changes: 9 additions & 0 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -3127,6 +3127,9 @@ def __str__(self) -> str:
| {f"COM{c}" for c in "123456789\xb9\xb2\xb3"}
| {f"LPT{c}" for c in "123456789\xb9\xb2\xb3"}
)
_WIN_RESERVED_CHARS = frozenset(
{chr(i) for i in range(32)} | {'"', "*", ":", "<", ">", "?", "|", "/", "\\"}
)

def isreserved(self, path):
if not self.is_windows_fs:
Expand All @@ -3137,6 +3140,12 @@ def is_reserved_name(name):
from os.path import _isreservedname # type: ignore[import-error]

return _isreservedname(name)

if name[-1:] in (".", " "):
return name not in (".", "..")
if self._WIN_RESERVED_CHARS.intersection(name):
return True
name = name.partition(".")[0].rstrip(" ").upper()
return name in self._WIN_RESERVED_NAMES

path = os.fsdecode(self.splitroot(path)[2])
Expand Down
1 change: 1 addition & 0 deletions pyfakefs/fake_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def fake_open(
if is_called_from_skipped_module(
skip_names=skip_names,
case_sensitive=filesystem.is_case_sensitive,
check_open_code=sys.version_info >= (3, 12),
):
return io_open( # pytype: disable=wrong-arg-count
file,
Expand Down
79 changes: 47 additions & 32 deletions pyfakefs/fake_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
from pyfakefs.helpers import IS_PYPY, is_called_from_skipped_module, FSType


_WIN_RESERVED_NAMES = (
{"CON", "PRN", "AUX", "NUL"}
| {"COM%d" % i for i in range(1, 10)}
| {"LPT%d" % i for i in range(1, 10)}
)


def init_module(filesystem):
"""Initializes the fake module with the fake file system."""
# pylint: disable=protected-access
Expand Down Expand Up @@ -433,11 +440,6 @@ class _FakeWindowsFlavour(_FakeFlavour):
implementations independent of FakeFilesystem properties.
"""

reserved_names = (
{"CON", "PRN", "AUX", "NUL"}
| {"COM%d" % i for i in range(1, 10)}
| {"LPT%d" % i for i in range(1, 10)}
)
sep = "\\"
altsep = "/"
has_drv = True
Expand All @@ -455,7 +457,7 @@ def is_reserved(self, parts):
if self.filesystem.is_windows_fs and parts[0].startswith("\\\\"):
# UNC paths are never reserved
return False
return parts[-1].partition(".")[0].upper() in self.reserved_names
return parts[-1].partition(".")[0].upper() in _WIN_RESERVED_NAMES

def make_uri(self, path):
"""Return a file URI for the given path"""
Expand Down Expand Up @@ -848,33 +850,15 @@ def touch(self, mode=0o666, exist_ok=True):
fake_file.close()
self.chmod(mode)

if sys.version_info >= (3, 12):
"""These are reimplemented for now because the original implementation
checks the flavour against ntpath/posixpath.
"""

def is_absolute(self):
if self.filesystem.is_windows_fs:
return self.drive and self.root
return os.path.isabs(self._path())

def is_reserved(self):
if sys.version_info >= (3, 13):
warnings.warn(
"pathlib.PurePath.is_reserved() is deprecated and scheduled "
"for removal in Python 3.15. Use os.path.isreserved() to detect "
"reserved paths on Windows.",
DeprecationWarning,
)
if not self.filesystem.is_windows_fs:
return False
if sys.version_info < (3, 13):
if not self._tail or self._tail[0].startswith("\\\\"):
# UNC paths are never reserved.
return False
name = self._tail[-1].partition(".")[0].partition(":")[0].rstrip(" ")
return name.upper() in pathlib._WIN_RESERVED_NAMES
return self.filesystem.isreserved(self._path())
def _warn_is_reserved_deprecated():
if sys.version_info >= (3, 13):
warnings.warn(
"pathlib.PurePath.is_reserved() is deprecated and scheduled "
"for removal in Python 3.15. Use os.path.isreserved() to detect "
"reserved paths on Windows.",
DeprecationWarning,
)


class FakePathlibModule:
Expand All @@ -900,12 +884,43 @@ class PurePosixPath(PurePath):
paths"""

__slots__ = ()
if sys.version_info >= (3, 12):

def is_reserved(self):
_warn_is_reserved_deprecated()
return False

def is_absolute(self):
with os.path.filesystem.use_fs_type(FSType.POSIX):
return os.path.isabs(self)

class PureWindowsPath(PurePath):
"""A subclass of PurePath, that represents Windows filesystem paths"""

__slots__ = ()

if sys.version_info >= (3, 12):
"""These are reimplemented because the PurePath implementation
checks the flavour against ntpath/posixpath.
"""

def is_reserved(self):
_warn_is_reserved_deprecated()
if sys.version_info < (3, 13):
if not self._tail or self._tail[0].startswith("\\\\"):
# UNC paths are never reserved.
return False
name = (
self._tail[-1].partition(".")[0].partition(":")[0].rstrip(" ")
)
return name.upper() in _WIN_RESERVED_NAMES
with os.path.filesystem.use_fs_type(FSType.WINDOWS):
return os.path.isreserved(self)

def is_absolute(self):
with os.path.filesystem.use_fs_type(FSType.WINDOWS):
return bool(self.drive and self.root)

class WindowsPath(FakePath, PureWindowsPath):
"""A subclass of Path and PureWindowsPath that represents
concrete Windows filesystem paths.
Expand Down
14 changes: 12 additions & 2 deletions pyfakefs/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,12 +478,19 @@ def putvalue(self, value: bytes) -> None:
self._bytestream.write(value)


def is_called_from_skipped_module(skip_names: list, case_sensitive: bool) -> bool:
def is_called_from_skipped_module(
skip_names: list, case_sensitive: bool, check_open_code: bool = False
) -> bool:
def starts_with(path, string):
if case_sensitive:
return path.startswith(string)
return path.lower().startswith(string.lower())

# in most cases we don't have skip names and won't need the overhead
# of analyzing the traceback, except when checking for open_code
if not skip_names and not check_open_code:
return False

stack = traceback.extract_stack()

# handle the case that we try to call the original `open_code`
Expand All @@ -494,12 +501,15 @@ def starts_with(path, string):
# -3: fake_io.open: 'return fake_open('
# -4: fake_io.open_code : 'return self._io_module.open_code(path)'
if (
sys.version_info >= (3, 12)
check_open_code
and stack[-4].name == "open_code"
and stack[-4].line == "return self._io_module.open_code(path)"
):
return True

if not skip_names:
return False

caller_filename = next(
(
frame.filename
Expand Down

0 comments on commit 2325e39

Please sign in to comment.