Skip to content

Commit f9e9fcd

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 f9e9fcd

11 files changed

+516
-332
lines changed

.github/workflows/testsuite.yml

+1-2
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

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ 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)
2021

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

pyfakefs/fake_filesystem.py

+1
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

pyfakefs/fake_filesystem_unittest.py

+3-1
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

+106-1
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,62 @@ 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+
p = os.fspath(path)
206+
if isinstance(p, bytes):
207+
sep = self.filesystem.path_separator.encode()
208+
altsep = None
209+
if self.filesystem.alternative_path_separator:
210+
altsep = self.filesystem.alternative_path_separator.encode()
211+
colon = b":"
212+
unc_prefix = b"\\\\?\\UNC\\"
213+
empty = b""
214+
else:
215+
sep = self.filesystem.path_separator
216+
altsep = self.filesystem.alternative_path_separator
217+
colon = ":"
218+
unc_prefix = "\\\\?\\UNC\\"
219+
empty = ""
220+
if self.filesystem.is_windows_fs:
221+
normp = p.replace(altsep, sep) if altsep else p
222+
if normp[:1] == sep:
223+
if normp[1:2] == sep:
224+
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
225+
# Device drives, e.g. \\.\device or \\?\device
226+
start = 8 if normp[:8].upper() == unc_prefix else 2
227+
index = normp.find(sep, start)
228+
if index == -1:
229+
return p, empty, empty
230+
index2 = normp.find(sep, index + 1)
231+
if index2 == -1:
232+
return p, empty, empty
233+
return p[:index2], p[index2 : index2 + 1], p[index2 + 1 :]
234+
else:
235+
# Relative path with root, e.g. \Windows
236+
return empty, p[:1], p[1:]
237+
elif normp[1:2] == colon:
238+
if normp[2:3] == sep:
239+
# Absolute drive-letter path, e.g. X:\Windows
240+
return p[:2], p[2:3], p[3:]
241+
else:
242+
# Relative path with drive, e.g. X:Windows
243+
return p[:2], empty, p[2:]
244+
else:
245+
# Relative path, e.g. Windows
246+
return empty, empty, p
247+
else:
248+
if p[:1] != sep:
249+
# Relative path, e.g.: 'foo'
250+
return empty, empty, p
251+
elif p[1:2] != sep or p[2:3] == sep:
252+
# Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
253+
return empty, sep, p[1:]
254+
else:
255+
return empty, p[:2], p[2:]
256+
201257
def getmtime(self, path: AnyStr) -> float:
202258
"""Returns the modification time of the fake file.
203259
@@ -473,3 +529,52 @@ def ismount(self, path: AnyStr) -> bool:
473529
def __getattr__(self, name: str) -> Any:
474530
"""Forwards any non-faked calls to the real os.path."""
475531
return getattr(self._os_path, name)
532+
533+
534+
if sys.platform == "win32":
535+
536+
class FakeNtModule:
537+
"""Under windows, a few function of `os.path` are taken from the `nt` module
538+
for performance reasons. These are patched here.
539+
"""
540+
541+
@staticmethod
542+
def dir():
543+
if sys.version_info >= (3, 12):
544+
return ["_path_exists", "_path_isfile", "_path_isdir", "_path_islink"]
545+
else:
546+
return ["_isdir"]
547+
548+
def __init__(self, filesystem: "FakeFilesystem"):
549+
"""Init.
550+
551+
Args:
552+
filesystem: FakeFilesystem used to provide file system information
553+
"""
554+
import nt
555+
556+
self.filesystem = filesystem
557+
self.nt_module: Any = nt
558+
559+
if sys.version_info >= (3, 12):
560+
561+
def _path_isdir(self, path: AnyStr) -> bool:
562+
return self.filesystem.isdir(path)
563+
564+
def _path_isfile(self, path: AnyStr) -> bool:
565+
return self.filesystem.isfile(path)
566+
567+
def _path_islink(self, path: AnyStr) -> bool:
568+
return self.filesystem.islink(path)
569+
570+
def _path_exists(self, path: AnyStr) -> bool:
571+
return self.filesystem.exists(path)
572+
573+
else:
574+
575+
def _isdir(self, path: AnyStr) -> bool:
576+
return self.filesystem.isdir(path)
577+
578+
def __getattr__(self, name: str) -> Any:
579+
"""Forwards any non-faked calls to the real nt module."""
580+
return getattr(self.nt_module, name)

0 commit comments

Comments
 (0)