Skip to content

Commit 4e99991

Browse files
committed
Fix patching open for Python 3.12
- 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
1 parent ba4f217 commit 4e99991

File tree

7 files changed

+33
-21
lines changed

7 files changed

+33
-21
lines changed

.github/workflows/testsuite.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
fail-fast: false
2626
matrix:
2727
os: [ubuntu-latest, macOS-latest, windows-latest]
28-
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12-dev"]
28+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
2929
include:
3030
- python-version: "pypy-3.7"
3131
os: ubuntu-latest

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ The released versions correspond to PyPI releases.
33

44
## Unreleased
55

6+
### Changes
7+
* add official support for Python 3.12
8+
69
### Fixes
710
* removed a leftover debug print statement (see [#869](../../issues/869))
811
* make sure tests work without HOME environment set (see [#870](../../issues/870))
912
* automount drive or UNC path under Windows if needed for `pathlib.Path.mkdir()`
1013
(see [#890](../../issues/890))
14+
* adapt patching `io.open` to work with Python 3.12 (see [#836](../../issues/836))
1115

1216
## [Version 5.2.3](https://pypi.python.org/pypi/pyfakefs/5.2.3) (2023-08-18)
1317
Fixes a rare problem on pytest shutdown.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ for more information about the limitations of pyfakefs.
7373
### Continuous integration
7474

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

7979
### Running pyfakefs unit tests

pyfakefs/fake_filesystem_unittest.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
to `:py:class`pyfakefs.fake_filesystem_unittest.TestCase`.
3737
"""
3838
import _io # type:ignore[import]
39-
import builtins
4039
import doctest
4140
import functools
4241
import genericpath
@@ -562,7 +561,6 @@ def __init__(
562561
# save the original open function for use in pytest plugin
563562
self.original_open = open
564563
self.patch_open_code = patch_open_code
565-
self.fake_open: fake_open.FakeFileOpen
566564

567565
if additional_skip_names is not None:
568566
skip_names = [
@@ -655,8 +653,8 @@ def _init_fake_module_classes(self) -> None:
655653
"io": fake_io.FakeIoModule,
656654
"pathlib": fake_pathlib.FakePathlibModule,
657655
}
658-
if IS_PYPY:
659-
# in PyPy io.open, the module is referenced as _io
656+
if IS_PYPY or sys.version_info >= (3, 12):
657+
# in PyPy and later cpython versions, the module is referenced as _io
660658
self._fake_module_classes["_io"] = fake_io.FakeIoModule
661659
if sys.platform == "win32":
662660
self._fake_module_classes["nt"] = fake_path.FakeNtModule
@@ -853,7 +851,6 @@ def _refresh(self) -> None:
853851

854852
self.fs = fake_filesystem.FakeFilesystem(patcher=self, create_temp_dir=True)
855853
self.fs.patch_open_code = self.patch_open_code
856-
self.fake_open = fake_open.FakeFileOpen(self.fs)
857854
for name in self._fake_module_classes:
858855
self.fake_modules[name] = self._fake_module_classes[name](self.fs)
859856
if hasattr(self.fake_modules[name], "skip_names"):
@@ -932,9 +929,6 @@ def patch_modules(self) -> None:
932929
for module, attr in modules:
933930
if attr in self.unfaked_modules:
934931
self._stubs.smart_set(module, name, self.unfaked_modules[attr])
935-
if sys.version_info >= (3, 12):
936-
# workaround for patching open - does not work with skip modules
937-
self._stubs.smart_set(builtins, "open", self.fake_open)
938932

939933
def patch_defaults(self) -> None:
940934
for fct, idx, ft in self.FS_DEFARGS:

pyfakefs/fake_io.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,16 @@ def open(
9595
# specific modules; instead we check if the caller is a skipped
9696
# module (should work in most cases)
9797
stack = traceback.extract_stack(limit=2)
98+
# handle the case that we try to call the original `open_code`
99+
# and get here instead (since Python 3.12)
100+
from_open_code = (
101+
sys.version_info >= (3, 12)
102+
and stack[0].name == "open_code"
103+
and stack[0].line == "return self._io_module.open_code(path)"
104+
)
98105
module_name = os.path.splitext(stack[0].filename)[0]
99106
module_name = module_name.replace(os.sep, ".")
100-
if any(
107+
if from_open_code or any(
101108
[
102109
module_name == sn or module_name.endswith("." + sn)
103110
for sn in self.skip_names

pyfakefs/tests/fake_filesystem_unittest_test.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Test the :py:class`pyfakefs.fake_filesystem_unittest.TestCase` base class.
1919
"""
2020
import glob
21+
import importlib.util
2122
import io
2223
import multiprocessing
2324
import os
@@ -28,6 +29,8 @@
2829
import tempfile
2930
import unittest
3031
import warnings
32+
from contextlib import redirect_stdout
33+
from io import StringIO
3134
from pathlib import Path
3235
from unittest import TestCase, mock
3336

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

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

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

401-
@unittest.skipIf(
402-
sys.version_info >= (3, 12),
403-
"Skip modules currently not working for open in 3.12",
404-
)
405402
def test_open_succeeds(self):
406403
pyfakefs.tests.import_as_example.open_this_file()
407404

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

450-
@unittest.skipIf(
451-
sys.version_info >= (3, 12),
452-
"Skip modules currently not working for open in 3.12",
453-
)
454447
def test_open_succeeds(self):
455448
pyfakefs.tests.import_as_example.open_this_file()
456449

@@ -787,6 +780,7 @@ def load_configs(configs):
787780
return retval
788781

789782

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

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

803+
def import_foo(self):
804+
spec = importlib.util.spec_from_file_location("bar", "/foo/bar.py")
805+
mod = importlib.util.module_from_spec(spec)
806+
spec.loader.exec_module(mod)
807+
808+
@unittest.skipIf(sys.platform == "win32", "Not yet working under Windows")
809+
def test_exec_module_in_fake_fs(self):
810+
self.fs.create_file("/foo/bar.py", contents="print('hello')")
811+
with redirect_stdout(StringIO()) as stdout:
812+
self.import_foo()
813+
assert stdout.getvalue() == "hello\n"
814+
809815

810816
class TestOtherFS(fake_filesystem_unittest.TestCase):
811817
def setUp(self):

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ classifiers =
3333
Programming Language :: Python :: 3.9
3434
Programming Language :: Python :: 3.10
3535
Programming Language :: Python :: 3.11
36+
Programming Language :: Python :: 3.12
3637
Programming Language :: Python :: Implementation :: CPython
3738
Programming Language :: Python :: Implementation :: PyPy
3839
Operating System :: POSIX

0 commit comments

Comments
 (0)