54 lines
1.5 KiB
Python
54 lines
1.5 KiB
Python
"""Convenience layer on top of stdlib's shutil and os"""
|
|
|
|
import os
|
|
import stat
|
|
from typing import Callable, TypeVar
|
|
|
|
from .compat import py311
|
|
|
|
from distutils import log
|
|
|
|
try:
|
|
from os import chmod # pyright: ignore[reportAssignmentType]
|
|
# Losing type-safety w/ pyright, but that's ok
|
|
except ImportError: # pragma: no cover
|
|
# Jython compatibility
|
|
def chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway
|
|
pass
|
|
|
|
|
|
_T = TypeVar("_T")
|
|
|
|
|
|
def attempt_chmod_verbose(path, mode):
|
|
log.debug("changing mode of %s to %o", path, mode)
|
|
try:
|
|
chmod(path, mode)
|
|
except OSError as e: # pragma: no cover
|
|
log.debug("chmod failed: %s", e)
|
|
|
|
|
|
# Must match shutil._OnExcCallback
|
|
def _auto_chmod(
|
|
func: Callable[..., _T], arg: str, exc: BaseException
|
|
) -> _T: # pragma: no cover
|
|
"""shutils onexc callback to automatically call chmod for certain functions."""
|
|
# Only retry for scenarios known to have an issue
|
|
if func in [os.unlink, os.remove] and os.name == 'nt':
|
|
attempt_chmod_verbose(arg, stat.S_IWRITE)
|
|
return func(arg)
|
|
raise exc
|
|
|
|
|
|
def rmtree(path, ignore_errors=False, onexc=_auto_chmod):
|
|
"""
|
|
Similar to ``shutil.rmtree`` but automatically executes ``chmod``
|
|
for well know Windows failure scenarios.
|
|
"""
|
|
return py311.shutil_rmtree(path, ignore_errors, onexc)
|
|
|
|
|
|
def rmdir(path, **opts):
|
|
if os.path.isdir(path):
|
|
rmtree(path, **opts)
|