pathlib: fix Python 3.12 rmtree(onerror=...) deprecation
Fixes #10890 Ref: https://docs.python.org/3.12/library/shutil.html#shutil.rmtree
This commit is contained in:
parent
22524046cf
commit
1b196fbeaf
|
@ -0,0 +1 @@
|
|||
Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`.
|
|
@ -6,6 +6,7 @@ import itertools
|
|||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import types
|
||||
import uuid
|
||||
import warnings
|
||||
from enum import Enum
|
||||
|
@ -28,6 +29,8 @@ from typing import Iterable
|
|||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
|
@ -63,21 +66,33 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
|
|||
return path.joinpath(".lock")
|
||||
|
||||
|
||||
def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
||||
def on_rm_rf_error(
|
||||
func,
|
||||
path: str,
|
||||
excinfo: Union[
|
||||
BaseException,
|
||||
Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]],
|
||||
],
|
||||
*,
|
||||
start_path: Path,
|
||||
) -> bool:
|
||||
"""Handle known read-only errors during rmtree.
|
||||
|
||||
The returned value is used only by our own tests.
|
||||
"""
|
||||
exctype, excvalue = exc[:2]
|
||||
if isinstance(excinfo, BaseException):
|
||||
exc = excinfo
|
||||
else:
|
||||
exc = excinfo[1]
|
||||
|
||||
# Another process removed the file in the middle of the "rm_rf" (xdist for example).
|
||||
# More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
|
||||
if isinstance(excvalue, FileNotFoundError):
|
||||
if isinstance(exc, FileNotFoundError):
|
||||
return False
|
||||
|
||||
if not isinstance(excvalue, PermissionError):
|
||||
if not isinstance(exc, PermissionError):
|
||||
warnings.warn(
|
||||
PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}")
|
||||
PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}")
|
||||
)
|
||||
return False
|
||||
|
||||
|
@ -86,7 +101,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
|||
warnings.warn(
|
||||
PytestWarning(
|
||||
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
|
||||
func, path, exctype, excvalue
|
||||
func, path, type(exc), exc
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -149,7 +164,10 @@ def rm_rf(path: Path) -> None:
|
|||
are read-only."""
|
||||
path = ensure_extended_length_path(path)
|
||||
onerror = partial(on_rm_rf_error, start_path=path)
|
||||
shutil.rmtree(str(path), onerror=onerror)
|
||||
if sys.version_info >= (3, 12):
|
||||
shutil.rmtree(str(path), onexc=onerror)
|
||||
else:
|
||||
shutil.rmtree(str(path), onerror=onerror)
|
||||
|
||||
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
|
||||
|
|
|
@ -512,20 +512,20 @@ class TestRmRf:
|
|||
|
||||
# unknown exception
|
||||
with pytest.warns(pytest.PytestWarning):
|
||||
exc_info1 = (None, RuntimeError(), None)
|
||||
exc_info1 = (RuntimeError, RuntimeError(), None)
|
||||
on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
|
||||
# we ignore FileNotFoundError
|
||||
exc_info2 = (None, FileNotFoundError(), None)
|
||||
exc_info2 = (FileNotFoundError, FileNotFoundError(), None)
|
||||
assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path)
|
||||
|
||||
# unknown function
|
||||
with pytest.warns(
|
||||
pytest.PytestWarning,
|
||||
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ",
|
||||
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n<class 'PermissionError'>: ",
|
||||
):
|
||||
exc_info3 = (None, PermissionError(), None)
|
||||
exc_info3 = (PermissionError, PermissionError(), None)
|
||||
on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
|
||||
|
@ -533,12 +533,12 @@ class TestRmRf:
|
|||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
with pytest.warns(None) as warninfo: # type: ignore[call-overload]
|
||||
exc_info4 = (None, PermissionError(), None)
|
||||
exc_info4 = PermissionError()
|
||||
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
assert not [x.message for x in warninfo]
|
||||
|
||||
exc_info5 = (None, PermissionError(), None)
|
||||
exc_info5 = PermissionError()
|
||||
on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)
|
||||
assert not fn.is_file()
|
||||
|
||||
|
|
Loading…
Reference in New Issue