Skip to content

Commit

Permalink
Add support for current Python 3.12
Browse files Browse the repository at this point in the history
- adapt for changed pathlib implementation (removed flavour implementation)
- Windows: add patching for some os.path functions now implemented in nt instead of ntpath
- fix handling of devnull for changed OS
- add fake implementation for os.path.splitroot
- fixes pytest-dev#770
  • Loading branch information
mrbean-bremen committed Mar 30, 2023
1 parent fa36aee commit 83e6841
Show file tree
Hide file tree
Showing 12 changed files with 569 additions and 332 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
# python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
include:
- python-version: "pypy-3.7"
os: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ The released versions correspond to PyPI releases.
### Features
* added possibility to set a path inaccessible under Windows by using `chown()` with
the `force_unix_mode` flag (see [#720](../../issues/720))
* added support for changes in Python 3.12 (currently in last beta version)
* added support for `os.path.splitroot` (new in Python 3.12)

## [Version 5.1.0](https://pypi.python.org/pypi/pyfakefs/5.1.0) (2023-01-12)
New version before Debian freeze
Expand Down
57 changes: 57 additions & 0 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ def reset(self, total_size: Optional[int] = None):
"""Remove all file system contents and reset the root."""
self.root = FakeDirectory(self.path_separator, filesystem=self)

self.dev_null = FakeNullFile(self)
self.open_files = []
self._free_fd_heap = []
self.last_ino = 0
Expand Down Expand Up @@ -1071,6 +1072,62 @@ def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
return path_str[:2], path_str[2:]
return path_str[:0], path_str

def splitroot(self, path: AnyStr):
"""Split a pathname into drive, root and tail.
Implementation taken from ntpath and posixpath.
"""
p = os.fspath(path)
if isinstance(p, bytes):
sep = self.path_separator.encode()
altsep = None
if self.alternative_path_separator:
altsep = self.alternative_path_separator.encode()
colon = b":"
unc_prefix = b"\\\\?\\UNC\\"
empty = b""
else:
sep = self.path_separator
altsep = self.alternative_path_separator
colon = ":"
unc_prefix = "\\\\?\\UNC\\"
empty = ""
if self.is_windows_fs:
normp = p.replace(altsep, sep) if altsep else p
if normp[:1] == sep:
if normp[1:2] == sep:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, empty, empty
index2 = normp.find(sep, index + 1)
if index2 == -1:
return p, empty, empty
return p[:index2], p[index2 : index2 + 1], p[index2 + 1 :]
else:
# Relative path with root, e.g. \Windows
return empty, p[:1], p[1:]
elif normp[1:2] == colon:
if normp[2:3] == sep:
# Absolute drive-letter path, e.g. X:\Windows
return p[:2], p[2:3], p[3:]
else:
# Relative path with drive, e.g. X:Windows
return p[:2], empty, p[2:]
else:
# Relative path, e.g. Windows
return empty, empty, p
else:
if p[:1] != sep:
# Relative path, e.g.: 'foo'
return empty, empty, p
elif p[1:2] != sep or p[2:3] == sep:
# Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
return empty, sep, p[1:]
else:
return empty, p[:2], p[2:]

def _join_paths_with_drive_support(self, *all_paths: AnyStr) -> AnyStr:
"""Taken from Python 3.5 os.path.join() code in ntpath.py
and slightly adapted"""
Expand Down
4 changes: 3 additions & 1 deletion pyfakefs/fake_filesystem_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,9 @@ def _init_fake_module_classes(self) -> None:
if IS_PYPY:
# in PyPy io.open, the module is referenced as _io
self._fake_module_classes["_io"] = fake_io.FakeIoModule
if sys.platform != "win32":
if sys.platform == "win32":
self._fake_module_classes["nt"] = fake_path.FakeNtModule
else:
self._fake_module_classes["fcntl"] = fake_filesystem.FakeFcntlModule

# class modules maps class names against a list of modules they can
Expand Down
57 changes: 56 additions & 1 deletion pyfakefs/fake_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def dir() -> List[str]:
"samefile",
]
if sys.version_info >= (3, 12):
dir_list.append("isjunction")
dir_list += ["isjunction", "splitroot"]
return dir_list

def __init__(self, filesystem: "FakeFilesystem", os_module: "FakeOsModule"):
Expand Down Expand Up @@ -198,6 +198,12 @@ def isjunction(self, path: AnyStr) -> bool:
"""Returns False. Junctions are never faked."""
return self.filesystem.isjunction(path)

def splitroot(self, path: AnyStr):
"""Split a pathname into drive, root and tail.
Implementation taken from ntpath and posixpath.
"""
return self.filesystem.splitroot(path)

def getmtime(self, path: AnyStr) -> float:
"""Returns the modification time of the fake file.
Expand Down Expand Up @@ -473,3 +479,52 @@ def ismount(self, path: AnyStr) -> bool:
def __getattr__(self, name: str) -> Any:
"""Forwards any non-faked calls to the real os.path."""
return getattr(self._os_path, name)


if sys.platform == "win32":

class FakeNtModule:
"""Under windows, a few function of `os.path` are taken from the `nt` module
for performance reasons. These are patched here.
"""

@staticmethod
def dir():
if sys.version_info >= (3, 12):
return ["_path_exists", "_path_isfile", "_path_isdir", "_path_islink"]
else:
return ["_isdir"]

def __init__(self, filesystem: "FakeFilesystem"):
"""Init.
Args:
filesystem: FakeFilesystem used to provide file system information
"""
import nt

self.filesystem = filesystem
self.nt_module: Any = nt

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

def _path_isdir(self, path: AnyStr) -> bool:
return self.filesystem.isdir(path)

def _path_isfile(self, path: AnyStr) -> bool:
return self.filesystem.isfile(path)

def _path_islink(self, path: AnyStr) -> bool:
return self.filesystem.islink(path)

def _path_exists(self, path: AnyStr) -> bool:
return self.filesystem.exists(path)

else:

def _isdir(self, path: AnyStr) -> bool:
return self.filesystem.isdir(path)

def __getattr__(self, name: str) -> Any:
"""Forwards any non-faked calls to the real nt module."""
return getattr(self.nt_module, name)
Loading

0 comments on commit 83e6841

Please sign in to comment.