Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for current Python 3.12 #798

Merged
merged 1 commit into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

@hugovk hugovk Mar 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3.12 is currently on alpha 6, with alpha 7 due on Monday and beta 1 due in May:

https://peps.python.org/pep-0693/#schedule

But thank you for this fix! It will make it easier for libraries to test against 3.12 and be ready for the full release.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I actually meant to write "last alpha version" - thanks!

* 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