Skip to content

Commit 199561f

Browse files
committed
Add support for current Python 3.12
- 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 #770
1 parent fa36aee commit 199561f

File tree

12 files changed

+569
-332
lines changed

12 files changed

+569
-332
lines changed

.github/workflows/testsuite.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
os: [ubuntu-latest, macOS-latest, windows-latest]
13-
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
14-
# python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
13+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
1514
include:
1615
- python-version: "pypy-3.7"
1716
os: ubuntu-latest

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ The released versions correspond to PyPI releases.
1717
### Features
1818
* added possibility to set a path inaccessible under Windows by using `chown()` with
1919
the `force_unix_mode` flag (see [#720](../../issues/720))
20+
* added support for changes in Python 3.12 (currently in last beta version)
21+
* added support for `os.path.splitroot` (new in Python 3.12)
2022

2123
## [Version 5.1.0](https://pypi.python.org/pypi/pyfakefs/5.1.0) (2023-01-12)
2224
New version before Debian freeze

pyfakefs/fake_filesystem.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ def reset(self, total_size: Optional[int] = None):
325325
"""Remove all file system contents and reset the root."""
326326
self.root = FakeDirectory(self.path_separator, filesystem=self)
327327

328+
self.dev_null = FakeNullFile(self)
328329
self.open_files = []
329330
self._free_fd_heap = []
330331
self.last_ino = 0
@@ -1071,6 +1072,62 @@ def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
10711072
return path_str[:2], path_str[2:]
10721073
return path_str[:0], path_str
10731074

1075+
def splitroot(self, path: AnyStr):
1076+
"""Split a pathname into drive, root and tail.
1077+
Implementation taken from ntpath and posixpath.
1078+
"""
1079+
p = os.fspath(path)
1080+
if isinstance(p, bytes):
1081+
sep = self.path_separator.encode()
1082+
altsep = None
1083+
if self.alternative_path_separator:
1084+
altsep = self.alternative_path_separator.encode()
1085+
colon = b":"
1086+
unc_prefix = b"\\\\?\\UNC\\"
1087+
empty = b""
1088+
else:
1089+
sep = self.path_separator
1090+
altsep = self.alternative_path_separator
1091+
colon = ":"
1092+
unc_prefix = "\\\\?\\UNC\\"
1093+
empty = ""
1094+
if self.is_windows_fs:
1095+
normp = p.replace(altsep, sep) if altsep else p
1096+
if normp[:1] == sep:
1097+
if normp[1:2] == sep:
1098+
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
1099+
# Device drives, e.g. \\.\device or \\?\device
1100+
start = 8 if normp[:8].upper() == unc_prefix else 2
1101+
index = normp.find(sep, start)
1102+
if index == -1:
1103+
return p, empty, empty
1104+
index2 = normp.find(sep, index + 1)
1105+
if index2 == -1:
1106+
return p, empty, empty
1107+
return p[:index2], p[index2 : index2 + 1], p[index2 + 1 :]
1108+
else:
1109+
# Relative path with root, e.g. \Windows
1110+
return empty, p[:1], p[1:]
1111+
elif normp[1:2] == colon:
1112+
if normp[2:3] == sep:
1113+
# Absolute drive-letter path, e.g. X:\Windows
1114+
return p[:2], p[2:3], p[3:]
1115+
else:
1116+
# Relative path with drive, e.g. X:Windows
1117+
return p[:2], empty, p[2:]
1118+
else:
1119+
# Relative path, e.g. Windows
1120+
return empty, empty, p
1121+
else:
1122+
if p[:1] != sep:
1123+
# Relative path, e.g.: 'foo'
1124+
return empty, empty, p
1125+
elif p[1:2] != sep or p[2:3] == sep:
1126+
# Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
1127+
return empty, sep, p[1:]
1128+
else:
1129+
return empty, p[:2], p[2:]
1130+
10741131
def _join_paths_with_drive_support(self, *all_paths: AnyStr) -> AnyStr:
10751132
"""Taken from Python 3.5 os.path.join() code in ntpath.py
10761133
and slightly adapted"""

pyfakefs/fake_filesystem_unittest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,9 @@ def _init_fake_module_classes(self) -> None:
653653
if IS_PYPY:
654654
# in PyPy io.open, the module is referenced as _io
655655
self._fake_module_classes["_io"] = fake_io.FakeIoModule
656-
if sys.platform != "win32":
656+
if sys.platform == "win32":
657+
self._fake_module_classes["nt"] = fake_path.FakeNtModule
658+
else:
657659
self._fake_module_classes["fcntl"] = fake_filesystem.FakeFcntlModule
658660

659661
# class modules maps class names against a list of modules they can

pyfakefs/fake_path.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def dir() -> List[str]:
101101
"samefile",
102102
]
103103
if sys.version_info >= (3, 12):
104-
dir_list.append("isjunction")
104+
dir_list += ["isjunction", "splitroot"]
105105
return dir_list
106106

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

201+
def splitroot(self, path: AnyStr):
202+
"""Split a pathname into drive, root and tail.
203+
Implementation taken from ntpath and posixpath.
204+
"""
205+
return self.filesystem.splitroot(path)
206+
201207
def getmtime(self, path: AnyStr) -> float:
202208
"""Returns the modification time of the fake file.
203209
@@ -473,3 +479,52 @@ def ismount(self, path: AnyStr) -> bool:
473479
def __getattr__(self, name: str) -> Any:
474480
"""Forwards any non-faked calls to the real os.path."""
475481
return getattr(self._os_path, name)
482+
483+
484+
if sys.platform == "win32":
485+
486+
class FakeNtModule:
487+
"""Under windows, a few function of `os.path` are taken from the `nt` module
488+
for performance reasons. These are patched here.
489+
"""
490+
491+
@staticmethod
492+
def dir():
493+
if sys.version_info >= (3, 12):
494+
return ["_path_exists", "_path_isfile", "_path_isdir", "_path_islink"]
495+
else:
496+
return ["_isdir"]
497+
498+
def __init__(self, filesystem: "FakeFilesystem"):
499+
"""Init.
500+
501+
Args:
502+
filesystem: FakeFilesystem used to provide file system information
503+
"""
504+
import nt
505+
506+
self.filesystem = filesystem
507+
self.nt_module: Any = nt
508+
509+
if sys.version_info >= (3, 12):
510+
511+
def _path_isdir(self, path: AnyStr) -> bool:
512+
return self.filesystem.isdir(path)
513+
514+
def _path_isfile(self, path: AnyStr) -> bool:
515+
return self.filesystem.isfile(path)
516+
517+
def _path_islink(self, path: AnyStr) -> bool:
518+
return self.filesystem.islink(path)
519+
520+
def _path_exists(self, path: AnyStr) -> bool:
521+
return self.filesystem.exists(path)
522+
523+
else:
524+
525+
def _isdir(self, path: AnyStr) -> bool:
526+
return self.filesystem.isdir(path)
527+
528+
def __getattr__(self, name: str) -> Any:
529+
"""Forwards any non-faked calls to the real nt module."""
530+
return getattr(self.nt_module, name)

0 commit comments

Comments
 (0)