about summary refs log tree commit diff
"""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)