Skip to content

Commit

Permalink
Deprecation of scandir and pathlib2
Browse files Browse the repository at this point in the history
- separate code and tests related to these packages
- add warning on first usage of a package function
  • Loading branch information
mrbean-bremen committed Apr 14, 2024
1 parent fab401e commit f43ffa6
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 157 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/extra_requirements.txt') }}
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}-${{ hashFiles('**/extra_requirements.txt') }}-${{ hashFiles('**/legacy_requirements.txt') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-pip-
Expand Down Expand Up @@ -82,6 +82,7 @@ jobs:
if: ${{ matrix.python-version != 'pypy-3.10' }}
run: |
pip install -r extra_requirements.txt
pip install -r legacy_requirements.txt
pip install zstandard cffi # needed to test #910
shell: bash
- name: Run unit tests with extra packages as non-root user
Expand Down Expand Up @@ -148,6 +149,7 @@ jobs:
run: |
pip install -r requirements.txt
pip install -r extra_requirements.txt
pip install -r legacy_requirements.txt
pip install pytest-find-dependencies
- name: Check dependencies
run: python -m pytest --find-dependencies pyfakefs/tests
Expand Down
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# pyfakefs Release Notes
The released versions correspond to PyPI releases.

## Planned changes for next major release (6.0.0)
* remove support for patching legacy modules `scandir` and `pathlib2`
* remove support for Python 3.7

## Unreleased

### Changes
* The usage of the `pathlib2` and `scandir` modules in pyfakefs is now deprecated.
They will now cause deprecation warnings if still used. Support for patching
these modules will be removed in pyfakefs 6.0.

## [Version 5.4.1](https://pypi.python.org/pypi/pyfakefs/5.4.0) (2024-04-11)
Fixes a regression.

Expand Down
14 changes: 1 addition & 13 deletions extra_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will
# use them if available when running on older Python versions.
#
# They are dependencies of pytest when Python < 3.6 so we sometimes get them via
# requirements.txt, this file makes them explicit dependencies for testing &
# development.
#
# Older versions might work ok, the versions chosen here are just the latest
# available at the time of writing.
pathlib2>=2.3.2
scandir>=1.8

# pandas + xlrd are used to test pandas-specific patches to allow
# these are used to test pandas-specific patches to allow
# pyfakefs to work with pandas
# we use the latest version to see any problems with new versions
pandas==1.3.5; python_version == '3.7' # pyup: ignore
Expand Down
8 changes: 8 additions & 0 deletions legacy_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# "pathlib2" and "scandir" are backports of new standard modules, pyfakefs will
# patch them if available when running on older Python versions.
#
# The modules are no longer for all required Python version, and only used for CI tests.
# Note that the usage of these modules is deprecated, and their support
# will be removed in pyfakefs 6.0
pathlib2>=2.3.2
scandir>=1.8
14 changes: 7 additions & 7 deletions pyfakefs/fake_filesystem_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,11 @@
from importlib import reload

from pyfakefs import fake_filesystem, fake_io, fake_os, fake_open, fake_path, fake_file
from pyfakefs import fake_legacy_modules
from pyfakefs import fake_filesystem_shutil
from pyfakefs import fake_pathlib
from pyfakefs import mox3_stubout
from pyfakefs.extra_packages import pathlib2, use_scandir

if use_scandir:
from pyfakefs import fake_scandir
from pyfakefs.legacy_packages import pathlib2, scandir

OS_MODULE = "nt" if sys.platform == "win32" else "posix"
PATH_MODULE = "ntpath" if sys.platform == "win32" else "posixpath"
Expand Down Expand Up @@ -701,13 +699,15 @@ def _init_fake_module_classes(self) -> None:
self._class_modules["Path"] = ["pathlib"]
self._unfaked_module_classes["pathlib"] = fake_pathlib.RealPathlibModule
if pathlib2:
self._fake_module_classes["pathlib2"] = fake_pathlib.FakePathlibModule
self._fake_module_classes["pathlib2"] = (
fake_legacy_modules.FakePathlib2Module
)
self._class_modules["Path"].append("pathlib2")
self._unfaked_module_classes["pathlib2"] = fake_pathlib.RealPathlibModule
if scandir:
self._fake_module_classes["scandir"] = fake_legacy_modules.FakeScanDirModule
self._fake_module_classes["Path"] = fake_pathlib.FakePathlibPathModule
self._unfaked_module_classes["Path"] = fake_pathlib.RealPathlibPathModule
if use_scandir:
self._fake_module_classes["scandir"] = fake_scandir.FakeScanDirModule

def _init_fake_module_functions(self) -> None:
# handle patching function imported separately like
Expand Down
110 changes: 110 additions & 0 deletions pyfakefs/fake_legacy_modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import warnings


from pyfakefs.fake_pathlib import FakePathlibModule
from pyfakefs.fake_scandir import scandir, walk


def legacy_warning(module_name):
msg = (
f"You are using the legacy package '{module_name}' instead of the "
f"built-in module."
"Patching this package will no longer be supported in pyfakefs >= 6"
)
warnings.warn(msg, category=DeprecationWarning)


class FakePathlib2Module(FakePathlibModule):
"""Uses FakeFilesystem to provide a fake pathlib module replacement.
for the `pathlib2` package available on PyPi.
The usage of `pathlib2` is deprecated and will no longer be supported
in future pyfakefs versions.
"""

has_warned = False

def __getattribute__(self, name):
attr = object.__getattribute__(self, name)
if hasattr(attr, "__call__") and not FakePathlib2Module.has_warned:
FakePathlib2Module.has_warned = True
legacy_warning("pathlib2")
return attr


class FakeScanDirModule:
"""Uses FakeFilesystem to provide a fake module replacement
for the `scandir` package available on PyPi.
The usage of the `scandir` package is deprecated and will no longer be supported
in future pyfakefs versions.
You need a fake_filesystem to use this:
`filesystem = fake_filesystem.FakeFilesystem()`
`fake_scandir_module = fake_filesystem.FakeScanDirModule(filesystem)`
"""

@staticmethod
def dir():
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
return "scandir", "walk"

def __init__(self, filesystem):
self.filesystem = filesystem

has_warned = False

def scandir(self, path="."):
"""Return an iterator of DirEntry objects corresponding to the entries
in the directory given by path.
Args:
path: Path to the target directory within the fake filesystem.
Returns:
an iterator to an unsorted list of os.DirEntry objects for
each entry in path.
Raises:
OSError: if the target is not a directory.
"""
if not self.has_warned:
self.__class__.has_warned = True
legacy_warning("scandir")
return scandir(self.filesystem, path)

def walk(self, top, topdown=True, onerror=None, followlinks=False):
"""Perform a walk operation over the fake filesystem.
Args:
top: The root directory from which to begin walk.
topdown: Determines whether to return the tuples with the root as
the first entry (`True`) or as the last, after all the child
directory tuples (`False`).
onerror: If not `None`, function which will be called to handle the
`os.error` instance provided when `os.listdir()` fails.
followlinks: If `True`, symbolic links are followed.
Yields:
(path, directories, nondirectories) for top and each of its
subdirectories. See the documentation for the builtin os module
for further details.
"""
if not self.has_warned:
self.__class__.has_warned = True
legacy_warning("scandir")

return walk(self.filesystem, top, topdown, onerror, followlinks)
4 changes: 1 addition & 3 deletions pyfakefs/fake_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
Set,
)

from pyfakefs.extra_packages import use_scandir
from pyfakefs.fake_file import (
FakeDirectory,
FakeDirWrapper,
Expand Down Expand Up @@ -122,6 +121,7 @@ def dir() -> List[str]:
"removedirs",
"rename",
"rmdir",
"scandir",
"stat",
"symlink",
"umask",
Expand All @@ -145,8 +145,6 @@ def dir() -> List[str]:
"getgid",
"getuid",
]
if use_scandir:
_dir += ["scandir"]
return _dir

def __init__(self, filesystem: "FakeFilesystem"):
Expand Down
7 changes: 1 addition & 6 deletions pyfakefs/fake_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
from urllib.parse import quote_from_bytes as urlquote_from_bytes

from pyfakefs import fake_scandir
from pyfakefs.extra_packages import use_scandir
from pyfakefs.fake_filesystem import FakeFilesystem
from pyfakefs.fake_open import FakeFileOpen
from pyfakefs.fake_os import FakeOsModule, use_original_os
Expand Down Expand Up @@ -109,9 +108,7 @@ class _FakeAccessor(accessor): # type: ignore[valid-type, misc]
)

listdir = _wrap_strfunc(FakeFilesystem.listdir)

if use_scandir:
scandir = _wrap_strfunc(fake_scandir.scandir)
scandir = _wrap_strfunc(fake_scandir.scandir)

if hasattr(os, "lchmod"):
lchmod = _wrap_strfunc(
Expand Down Expand Up @@ -775,8 +772,6 @@ def is_reserved(self):

class FakePathlibModule:
"""Uses FakeFilesystem to provide a fake pathlib module replacement.
Can be used to replace both the standard `pathlib` module and the
`pathlib2` package available on PyPi.
You need a fake_filesystem to use this:
`filesystem = fake_filesystem.FakeFilesystem()`
Expand Down
70 changes: 2 additions & 68 deletions pyfakefs/fake_scandir.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@
import os
import sys

from pyfakefs.extra_packages import use_scandir_package
from pyfakefs.helpers import to_string, make_string_path

if sys.version_info >= (3, 6):
BaseClass = os.PathLike
else:
BaseClass = object


class DirEntry(BaseClass):
class DirEntry(os.PathLike):
"""Emulates os.DirEntry. Note that we did not enforce keyword only
arguments."""

Expand Down Expand Up @@ -133,9 +127,7 @@ class ScanDirIter:
def __init__(self, filesystem, path):
self.filesystem = filesystem
if isinstance(path, int):
if not use_scandir_package and (
sys.version_info < (3, 7) or self.filesystem.is_windows_fs
):
if self.filesystem.is_windows_fs:
raise NotImplementedError(
"scandir does not support file descriptor " "path argument"
)
Expand Down Expand Up @@ -263,61 +255,3 @@ def do_walk(top_dir, top_most=False):
yield top_contents

return do_walk(make_string_path(to_string(top)), top_most=True)


class FakeScanDirModule:
"""Uses FakeFilesystem to provide a fake `scandir` module replacement.
.. Note:: The ``scandir`` function is a part of the standard ``os`` module
since Python 3.5. This class handles the separate ``scandir`` module
that is available on pypi.
You need a fake_filesystem to use this:
`filesystem = fake_filesystem.FakeFilesystem()`
`fake_scandir_module = fake_filesystem.FakeScanDirModule(filesystem)`
"""

@staticmethod
def dir():
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
return "scandir", "walk"

def __init__(self, filesystem):
self.filesystem = filesystem

def scandir(self, path="."):
"""Return an iterator of DirEntry objects corresponding to the entries
in the directory given by path.
Args:
path: Path to the target directory within the fake filesystem.
Returns:
an iterator to an unsorted list of os.DirEntry objects for
each entry in path.
Raises:
OSError: if the target is not a directory.
"""
return scandir(self.filesystem, path)

def walk(self, top, topdown=True, onerror=None, followlinks=False):
"""Perform a walk operation over the fake filesystem.
Args:
top: The root directory from which to begin walk.
topdown: Determines whether to return the tuples with the root as
the first entry (`True`) or as the last, after all the child
directory tuples (`False`).
onerror: If not `None`, function which will be called to handle the
`os.error` instance provided when `os.listdir()` fails.
followlinks: If `True`, symbolic links are followed.
Yields:
(path, directories, nondirectories) for top and each of its
subdirectories. See the documentation for the builtin os module
for further details.
"""
return walk(self.filesystem, top, topdown, onerror, followlinks)
19 changes: 4 additions & 15 deletions pyfakefs/extra_packages.py → pyfakefs/legacy_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# limitations under the License.

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

try:
Expand All @@ -20,18 +21,6 @@
pathlib2 = None

try:
import scandir

use_scandir_package = True
use_builtin_scandir = False
import scandir as scandir
except ImportError:
try:
from os import scandir # noqa: F401

use_builtin_scandir = True
use_scandir_package = False
except ImportError:
use_builtin_scandir = False
use_scandir_package = False

use_scandir = use_scandir_package or use_builtin_scandir
scandir = None
Loading

0 comments on commit f43ffa6

Please sign in to comment.