Skip to content

Commit 67d3d2b

Browse files
committed
Deprecation of scandir and pathlib2
- separate code and tests related to these packages - add warning on first usage of a package function
1 parent fab401e commit 67d3d2b

17 files changed

+284
-159
lines changed

.github/workflows/testsuite.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
uses: actions/cache@v4
5555
with:
5656
path: ${{ steps.pip-cache.outputs.dir }}
57-
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/extra_requirements.txt') }}
57+
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/extra_requirements.txt') }}-${{ hashFiles('**/legacy_requirements.txt') }}
5858
restore-keys: |
5959
${{ matrix.os }}-${{ matrix.python-version }}-pip-
6060
@@ -82,6 +82,7 @@ jobs:
8282
if: ${{ matrix.python-version != 'pypy-3.10' }}
8383
run: |
8484
pip install -r extra_requirements.txt
85+
pip install -r legacy_requirements.txt
8586
pip install zstandard cffi # needed to test #910
8687
shell: bash
8788
- name: Run unit tests with extra packages as non-root user
@@ -148,6 +149,7 @@ jobs:
148149
run: |
149150
pip install -r requirements.txt
150151
pip install -r extra_requirements.txt
152+
pip install -r legacy_requirements.txt
151153
pip install pytest-find-dependencies
152154
- name: Check dependencies
153155
run: python -m pytest --find-dependencies pyfakefs/tests

CHANGES.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# pyfakefs Release Notes
22
The released versions correspond to PyPI releases.
33

4+
## Planned changes for next major release (6.0.0)
5+
* remove support for patching legacy modules `scandir` and `pathlib2`
6+
* remove support for Python 3.7
7+
8+
## Unreleased
9+
10+
### Changes
11+
* The usage of the `pathlib2` and `scandir` modules in pyfakefs is now deprecated.
12+
They will now cause deprecation warnings if still used. Support for patching
13+
these modules will be removed in pyfakefs 6.0.
14+
415
## [Version 5.4.1](https://pypi.python.org/pypi/pyfakefs/5.4.0) (2024-04-11)
516
Fixes a regression.
617

docs/modules.rst

-2
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,3 @@ Faked module classes
5454
.. autoclass:: pyfakefs.fake_filesystem_shutil.FakeShutilModule
5555

5656
.. autoclass:: pyfakefs.fake_pathlib.FakePathlibModule
57-
58-
.. autoclass:: pyfakefs.fake_scandir.FakeScanDirModule

extra_requirements.txt

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,4 @@
1-
# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will
2-
# use them if available when running on older Python versions.
3-
#
4-
# They are dependencies of pytest when Python < 3.6 so we sometimes get them via
5-
# requirements.txt, this file makes them explicit dependencies for testing &
6-
# development.
7-
#
8-
# Older versions might work ok, the versions chosen here are just the latest
9-
# available at the time of writing.
10-
pathlib2>=2.3.2
11-
scandir>=1.8
12-
13-
# pandas + xlrd are used to test pandas-specific patches to allow
1+
# these are used to test pandas-specific patches to allow
142
# pyfakefs to work with pandas
153
# we use the latest version to see any problems with new versions
164
pandas==1.3.5; python_version == '3.7' # pyup: ignore

legacy_requirements.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will
2+
# patch them if available when running on older Python versions.
3+
#
4+
# The modules are no longer for all required Python version, and only used for CI tests.
5+
# Note that the usage of these modules is deprecated, and their support
6+
# will be removed in pyfakefs 6.0
7+
pathlib2>=2.3.2
8+
scandir>=1.8

pyfakefs/fake_filesystem_unittest.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,11 @@
8383
from importlib import reload
8484

8585
from pyfakefs import fake_filesystem, fake_io, fake_os, fake_open, fake_path, fake_file
86+
from pyfakefs import fake_legacy_modules
8687
from pyfakefs import fake_filesystem_shutil
8788
from pyfakefs import fake_pathlib
8889
from pyfakefs import mox3_stubout
89-
from pyfakefs.extra_packages import pathlib2, use_scandir
90-
91-
if use_scandir:
92-
from pyfakefs import fake_scandir
90+
from pyfakefs.legacy_packages import pathlib2, scandir
9391

9492
OS_MODULE = "nt" if sys.platform == "win32" else "posix"
9593
PATH_MODULE = "ntpath" if sys.platform == "win32" else "posixpath"
@@ -701,13 +699,15 @@ def _init_fake_module_classes(self) -> None:
701699
self._class_modules["Path"] = ["pathlib"]
702700
self._unfaked_module_classes["pathlib"] = fake_pathlib.RealPathlibModule
703701
if pathlib2:
704-
self._fake_module_classes["pathlib2"] = fake_pathlib.FakePathlibModule
702+
self._fake_module_classes["pathlib2"] = (
703+
fake_legacy_modules.FakePathlib2Module
704+
)
705705
self._class_modules["Path"].append("pathlib2")
706706
self._unfaked_module_classes["pathlib2"] = fake_pathlib.RealPathlibModule
707+
if scandir:
708+
self._fake_module_classes["scandir"] = fake_legacy_modules.FakeScanDirModule
707709
self._fake_module_classes["Path"] = fake_pathlib.FakePathlibPathModule
708710
self._unfaked_module_classes["Path"] = fake_pathlib.RealPathlibPathModule
709-
if use_scandir:
710-
self._fake_module_classes["scandir"] = fake_scandir.FakeScanDirModule
711711

712712
def _init_fake_module_functions(self) -> None:
713713
# handle patching function imported separately like

pyfakefs/fake_legacy_modules.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import warnings
14+
15+
16+
from pyfakefs.fake_pathlib import FakePathlibModule
17+
from pyfakefs.fake_scandir import scandir, walk
18+
19+
20+
def legacy_warning(module_name):
21+
msg = (
22+
f"You are using the legacy package '{module_name}' instead of the "
23+
f"built-in module."
24+
"Patching this package will no longer be supported in pyfakefs >= 6"
25+
)
26+
warnings.warn(msg, category=DeprecationWarning)
27+
28+
29+
class FakePathlib2Module(FakePathlibModule):
30+
"""Uses FakeFilesystem to provide a fake pathlib module replacement.
31+
for the `pathlib2` package available on PyPi.
32+
The usage of `pathlib2` is deprecated and will no longer be supported
33+
in future pyfakefs versions.
34+
"""
35+
36+
has_warned = False
37+
38+
def __getattribute__(self, name):
39+
attr = object.__getattribute__(self, name)
40+
if hasattr(attr, "__call__") and not FakePathlib2Module.has_warned:
41+
FakePathlib2Module.has_warned = True
42+
legacy_warning("pathlib2")
43+
return attr
44+
45+
46+
class FakeScanDirModule:
47+
"""Uses FakeFilesystem to provide a fake module replacement
48+
for the `scandir` package available on PyPi.
49+
50+
The usage of the `scandir` package is deprecated and will no longer be supported
51+
in future pyfakefs versions.
52+
53+
You need a fake_filesystem to use this:
54+
`filesystem = fake_filesystem.FakeFilesystem()`
55+
`fake_scandir_module = fake_filesystem.FakeScanDirModule(filesystem)`
56+
"""
57+
58+
@staticmethod
59+
def dir():
60+
"""Return the list of patched function names. Used for patching
61+
functions imported from the module.
62+
"""
63+
return "scandir", "walk"
64+
65+
def __init__(self, filesystem):
66+
self.filesystem = filesystem
67+
68+
has_warned = False
69+
70+
def scandir(self, path="."):
71+
"""Return an iterator of DirEntry objects corresponding to the entries
72+
in the directory given by path.
73+
74+
Args:
75+
path: Path to the target directory within the fake filesystem.
76+
77+
Returns:
78+
an iterator to an unsorted list of os.DirEntry objects for
79+
each entry in path.
80+
81+
Raises:
82+
OSError: if the target is not a directory.
83+
"""
84+
if not self.has_warned:
85+
self.__class__.has_warned = True
86+
legacy_warning("scandir")
87+
return scandir(self.filesystem, path)
88+
89+
def walk(self, top, topdown=True, onerror=None, followlinks=False):
90+
"""Perform a walk operation over the fake filesystem.
91+
92+
Args:
93+
top: The root directory from which to begin walk.
94+
topdown: Determines whether to return the tuples with the root as
95+
the first entry (`True`) or as the last, after all the child
96+
directory tuples (`False`).
97+
onerror: If not `None`, function which will be called to handle the
98+
`os.error` instance provided when `os.listdir()` fails.
99+
followlinks: If `True`, symbolic links are followed.
100+
101+
Yields:
102+
(path, directories, nondirectories) for top and each of its
103+
subdirectories. See the documentation for the builtin os module
104+
for further details.
105+
"""
106+
if not self.has_warned:
107+
self.__class__.has_warned = True
108+
legacy_warning("scandir")
109+
110+
return walk(self.filesystem, top, topdown, onerror, followlinks)

pyfakefs/fake_os.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
Set,
4141
)
4242

43-
from pyfakefs.extra_packages import use_scandir
4443
from pyfakefs.fake_file import (
4544
FakeDirectory,
4645
FakeDirWrapper,
@@ -122,6 +121,7 @@ def dir() -> List[str]:
122121
"removedirs",
123122
"rename",
124123
"rmdir",
124+
"scandir",
125125
"stat",
126126
"symlink",
127127
"umask",
@@ -145,8 +145,6 @@ def dir() -> List[str]:
145145
"getgid",
146146
"getuid",
147147
]
148-
if use_scandir:
149-
_dir += ["scandir"]
150148
return _dir
151149

152150
def __init__(self, filesystem: "FakeFilesystem"):

pyfakefs/fake_pathlib.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from urllib.parse import quote_from_bytes as urlquote_from_bytes
4545

4646
from pyfakefs import fake_scandir
47-
from pyfakefs.extra_packages import use_scandir
4847
from pyfakefs.fake_filesystem import FakeFilesystem
4948
from pyfakefs.fake_open import FakeFileOpen
5049
from pyfakefs.fake_os import FakeOsModule, use_original_os
@@ -109,9 +108,7 @@ class _FakeAccessor(accessor): # type: ignore[valid-type, misc]
109108
)
110109

111110
listdir = _wrap_strfunc(FakeFilesystem.listdir)
112-
113-
if use_scandir:
114-
scandir = _wrap_strfunc(fake_scandir.scandir)
111+
scandir = _wrap_strfunc(fake_scandir.scandir)
115112

116113
if hasattr(os, "lchmod"):
117114
lchmod = _wrap_strfunc(
@@ -775,8 +772,6 @@ def is_reserved(self):
775772

776773
class FakePathlibModule:
777774
"""Uses FakeFilesystem to provide a fake pathlib module replacement.
778-
Can be used to replace both the standard `pathlib` module and the
779-
`pathlib2` package available on PyPi.
780775
781776
You need a fake_filesystem to use this:
782777
`filesystem = fake_filesystem.FakeFilesystem()`

pyfakefs/fake_scandir.py

+2-68
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,10 @@
2020
import os
2121
import sys
2222

23-
from pyfakefs.extra_packages import use_scandir_package
2423
from pyfakefs.helpers import to_string, make_string_path
2524

26-
if sys.version_info >= (3, 6):
27-
BaseClass = os.PathLike
28-
else:
29-
BaseClass = object
3025

31-
32-
class DirEntry(BaseClass):
26+
class DirEntry(os.PathLike):
3327
"""Emulates os.DirEntry. Note that we did not enforce keyword only
3428
arguments."""
3529

@@ -133,9 +127,7 @@ class ScanDirIter:
133127
def __init__(self, filesystem, path):
134128
self.filesystem = filesystem
135129
if isinstance(path, int):
136-
if not use_scandir_package and (
137-
sys.version_info < (3, 7) or self.filesystem.is_windows_fs
138-
):
130+
if self.filesystem.is_windows_fs:
139131
raise NotImplementedError(
140132
"scandir does not support file descriptor " "path argument"
141133
)
@@ -263,61 +255,3 @@ def do_walk(top_dir, top_most=False):
263255
yield top_contents
264256

265257
return do_walk(make_string_path(to_string(top)), top_most=True)
266-
267-
268-
class FakeScanDirModule:
269-
"""Uses FakeFilesystem to provide a fake `scandir` module replacement.
270-
271-
.. Note:: The ``scandir`` function is a part of the standard ``os`` module
272-
since Python 3.5. This class handles the separate ``scandir`` module
273-
that is available on pypi.
274-
275-
You need a fake_filesystem to use this:
276-
`filesystem = fake_filesystem.FakeFilesystem()`
277-
`fake_scandir_module = fake_filesystem.FakeScanDirModule(filesystem)`
278-
"""
279-
280-
@staticmethod
281-
def dir():
282-
"""Return the list of patched function names. Used for patching
283-
functions imported from the module.
284-
"""
285-
return "scandir", "walk"
286-
287-
def __init__(self, filesystem):
288-
self.filesystem = filesystem
289-
290-
def scandir(self, path="."):
291-
"""Return an iterator of DirEntry objects corresponding to the entries
292-
in the directory given by path.
293-
294-
Args:
295-
path: Path to the target directory within the fake filesystem.
296-
297-
Returns:
298-
an iterator to an unsorted list of os.DirEntry objects for
299-
each entry in path.
300-
301-
Raises:
302-
OSError: if the target is not a directory.
303-
"""
304-
return scandir(self.filesystem, path)
305-
306-
def walk(self, top, topdown=True, onerror=None, followlinks=False):
307-
"""Perform a walk operation over the fake filesystem.
308-
309-
Args:
310-
top: The root directory from which to begin walk.
311-
topdown: Determines whether to return the tuples with the root as
312-
the first entry (`True`) or as the last, after all the child
313-
directory tuples (`False`).
314-
onerror: If not `None`, function which will be called to handle the
315-
`os.error` instance provided when `os.listdir()` fails.
316-
followlinks: If `True`, symbolic links are followed.
317-
318-
Yields:
319-
(path, directories, nondirectories) for top and each of its
320-
subdirectories. See the documentation for the builtin os module
321-
for further details.
322-
"""
323-
return walk(self.filesystem, top, topdown, onerror, followlinks)

pyfakefs/extra_packages.py pyfakefs/legacy_packages.py

+4-15
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
# limitations under the License.
1212

1313
"""Imports external packages that replace or emulate internal packages.
14-
If the external module is not present, the built-in module is imported.
14+
These packages are not needed with any current Python version,
15+
and their support in pyfakefs will be removed in a an upcoming release.
1516
"""
1617

1718
try:
@@ -20,18 +21,6 @@
2021
pathlib2 = None
2122

2223
try:
23-
import scandir
24-
25-
use_scandir_package = True
26-
use_builtin_scandir = False
24+
import scandir as scandir
2725
except ImportError:
28-
try:
29-
from os import scandir # noqa: F401
30-
31-
use_builtin_scandir = True
32-
use_scandir_package = False
33-
except ImportError:
34-
use_builtin_scandir = False
35-
use_scandir_package = False
36-
37-
use_scandir = use_scandir_package or use_builtin_scandir
26+
scandir = None

0 commit comments

Comments
 (0)