Skip to content

Commit

Permalink
Fix patching open for Python 3.12
Browse files Browse the repository at this point in the history
- add patching _io, as this is used for io
- add workaround for calling the real open_code
  (does not work yet under Windows)
- add Python 3.12 to supported Python versions
- fixes #836
  • Loading branch information
mrbean-bremen committed Oct 9, 2023
1 parent ba4f217 commit 4e99991
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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", "3.12-dev"]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
include:
- python-version: "pypy-3.7"
os: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ The released versions correspond to PyPI releases.

## Unreleased

### Changes
* add official support for Python 3.12

### Fixes
* removed a leftover debug print statement (see [#869](../../issues/869))
* make sure tests work without HOME environment set (see [#870](../../issues/870))
* automount drive or UNC path under Windows if needed for `pathlib.Path.mkdir()`
(see [#890](../../issues/890))
* adapt patching `io.open` to work with Python 3.12 (see [#836](../../issues/836))

## [Version 5.2.3](https://pypi.python.org/pypi/pyfakefs/5.2.3) (2023-08-18)
Fixes a rare problem on pytest shutdown.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ for more information about the limitations of pyfakefs.
### Continuous integration

pyfakefs is currently automatically tested on Linux, macOS and Windows, with
Python 3.7 to 3.11, and with PyPy3 on Linux, using
Python 3.7 to 3.12, and with PyPy3 on Linux, using
[GitHub Actions](https://github.com/pytest-dev/pyfakefs/actions).

### Running pyfakefs unit tests
Expand Down
10 changes: 2 additions & 8 deletions pyfakefs/fake_filesystem_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
to `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`.
"""
import _io # type:ignore[import]
import builtins
import doctest
import functools
import genericpath
Expand Down Expand Up @@ -562,7 +561,6 @@ def __init__(
# save the original open function for use in pytest plugin
self.original_open = open
self.patch_open_code = patch_open_code
self.fake_open: fake_open.FakeFileOpen

if additional_skip_names is not None:
skip_names = [
Expand Down Expand Up @@ -655,8 +653,8 @@ def _init_fake_module_classes(self) -> None:
"io": fake_io.FakeIoModule,
"pathlib": fake_pathlib.FakePathlibModule,
}
if IS_PYPY:
# in PyPy io.open, the module is referenced as _io
if IS_PYPY or sys.version_info >= (3, 12):
# in PyPy and later cpython versions, the module is referenced as _io
self._fake_module_classes["_io"] = fake_io.FakeIoModule
if sys.platform == "win32":
self._fake_module_classes["nt"] = fake_path.FakeNtModule
Expand Down Expand Up @@ -853,7 +851,6 @@ def _refresh(self) -> None:

self.fs = fake_filesystem.FakeFilesystem(patcher=self, create_temp_dir=True)
self.fs.patch_open_code = self.patch_open_code
self.fake_open = fake_open.FakeFileOpen(self.fs)
for name in self._fake_module_classes:
self.fake_modules[name] = self._fake_module_classes[name](self.fs)
if hasattr(self.fake_modules[name], "skip_names"):
Expand Down Expand Up @@ -932,9 +929,6 @@ def patch_modules(self) -> None:
for module, attr in modules:
if attr in self.unfaked_modules:
self._stubs.smart_set(module, name, self.unfaked_modules[attr])
if sys.version_info >= (3, 12):
# workaround for patching open - does not work with skip modules
self._stubs.smart_set(builtins, "open", self.fake_open)

def patch_defaults(self) -> None:
for fct, idx, ft in self.FS_DEFARGS:
Expand Down
9 changes: 8 additions & 1 deletion pyfakefs/fake_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,16 @@ def open(
# specific modules; instead we check if the caller is a skipped
# module (should work in most cases)
stack = traceback.extract_stack(limit=2)
# handle the case that we try to call the original `open_code`
# and get here instead (since Python 3.12)
from_open_code = (
sys.version_info >= (3, 12)
and stack[0].name == "open_code"
and stack[0].line == "return self._io_module.open_code(path)"
)
module_name = os.path.splitext(stack[0].filename)[0]
module_name = module_name.replace(os.sep, ".")
if any(
if from_open_code or any(
[
module_name == sn or module_name.endswith("." + sn)
for sn in self.skip_names
Expand Down
26 changes: 16 additions & 10 deletions pyfakefs/tests/fake_filesystem_unittest_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Test the :py:class`pyfakefs.fake_filesystem_unittest.TestCase` base class.
"""
import glob
import importlib.util
import io
import multiprocessing
import os
Expand All @@ -28,6 +29,8 @@
import tempfile
import unittest
import warnings
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from unittest import TestCase, mock

Expand Down Expand Up @@ -228,14 +231,12 @@ def test_import_function_from_os_as_other_name(self):
stat_result = pyfakefs.tests.import_as_example.file_stat2(file_path)
self.assertEqual(3, stat_result.st_size)

@unittest.skipIf(sys.version_info >= (3, 12), "Currently not working in 3.12")
def test_import_open_as_other_name(self):
file_path = "/foo/bar"
self.fs.create_file(file_path, contents=b"abc")
contents = pyfakefs.tests.import_as_example.file_contents1(file_path)
self.assertEqual("abc", contents)

@unittest.skipIf(sys.version_info >= (3, 12), "Currently not working in 3.12")
def test_import_io_open_as_other_name(self):
file_path = "/foo/bar"
self.fs.create_file(file_path, contents=b"abc")
Expand Down Expand Up @@ -398,10 +399,6 @@ def test_fake_path_does_not_exist7(self):
self.fs.create_file("foo")
self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists7("foo"))

@unittest.skipIf(
sys.version_info >= (3, 12),
"Skip modules currently not working for open in 3.12",
)
def test_open_succeeds(self):
pyfakefs.tests.import_as_example.open_this_file()

Expand Down Expand Up @@ -447,10 +444,6 @@ def test_fake_path_does_not_exist7(self):
self.fs.create_file("foo")
self.assertFalse(pyfakefs.tests.import_as_example.check_if_exists7("foo"))

@unittest.skipIf(
sys.version_info >= (3, 12),
"Skip modules currently not working for open in 3.12",
)
def test_open_succeeds(self):
pyfakefs.tests.import_as_example.open_this_file()

Expand Down Expand Up @@ -787,6 +780,7 @@ def load_configs(configs):
return retval


@unittest.skipIf(sys.version_info < (3, 8), "open_code new in Python 3.8")
class AutoPatchOpenCodeTestCase(fake_filesystem_unittest.TestCase):
"""Test patching open_code in auto mode, see issue #554."""

Expand All @@ -806,6 +800,18 @@ def test_run_path(self):
def test_run_module(self):
load_configs([self.config_module])

def import_foo(self):
spec = importlib.util.spec_from_file_location("bar", "/foo/bar.py")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)

@unittest.skipIf(sys.platform == "win32", "Not yet working under Windows")
def test_exec_module_in_fake_fs(self):
self.fs.create_file("/foo/bar.py", contents="print('hello')")
with redirect_stdout(StringIO()) as stdout:
self.import_foo()
assert stdout.getvalue() == "hello\n"


class TestOtherFS(fake_filesystem_unittest.TestCase):
def setUp(self):
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Operating System :: POSIX
Expand Down

0 comments on commit 4e99991

Please sign in to comment.