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 os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import types
|
||||||
import uuid
|
import uuid
|
||||||
import warnings
|
import warnings
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -28,6 +29,8 @@ from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import Type
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
@ -63,21 +66,33 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
|
||||||
return path.joinpath(".lock")
|
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.
|
"""Handle known read-only errors during rmtree.
|
||||||
|
|
||||||
The returned value is used only by our own tests.
|
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).
|
# 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
|
# More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
|
||||||
if isinstance(excvalue, FileNotFoundError):
|
if isinstance(exc, FileNotFoundError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not isinstance(excvalue, PermissionError):
|
if not isinstance(exc, PermissionError):
|
||||||
warnings.warn(
|
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
|
return False
|
||||||
|
|
||||||
|
@ -86,7 +101,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
PytestWarning(
|
PytestWarning(
|
||||||
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
|
"(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."""
|
are read-only."""
|
||||||
path = ensure_extended_length_path(path)
|
path = ensure_extended_length_path(path)
|
||||||
onerror = partial(on_rm_rf_error, start_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]:
|
def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
|
||||||
|
|
|
@ -512,20 +512,20 @@ class TestRmRf:
|
||||||
|
|
||||||
# unknown exception
|
# unknown exception
|
||||||
with pytest.warns(pytest.PytestWarning):
|
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)
|
on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path)
|
||||||
assert fn.is_file()
|
assert fn.is_file()
|
||||||
|
|
||||||
# we ignore FileNotFoundError
|
# 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)
|
assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path)
|
||||||
|
|
||||||
# unknown function
|
# unknown function
|
||||||
with pytest.warns(
|
with pytest.warns(
|
||||||
pytest.PytestWarning,
|
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)
|
on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path)
|
||||||
assert fn.is_file()
|
assert fn.is_file()
|
||||||
|
|
||||||
|
@ -533,12 +533,12 @@ class TestRmRf:
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
with pytest.warns(None) as warninfo: # type: ignore[call-overload]
|
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)
|
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
|
||||||
assert fn.is_file()
|
assert fn.is_file()
|
||||||
assert not [x.message for x in warninfo]
|
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)
|
on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)
|
||||||
assert not fn.is_file()
|
assert not fn.is_file()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue