|
|
"""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 |
|
|
|
|
|
except ImportError: |
|
|
|
|
|
def chmod(*args: object, **kwargs: object) -> None: |
|
|
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: |
|
|
log.debug("chmod failed: %s", e) |
|
|
|
|
|
|
|
|
|
|
|
def _auto_chmod( |
|
|
func: Callable[..., _T], arg: str, exc: BaseException |
|
|
) -> _T: |
|
|
"""shutils onexc callback to automatically call chmod for certain functions.""" |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
def current_umask(): |
|
|
tmp = os.umask(0o022) |
|
|
os.umask(tmp) |
|
|
return tmp |
|
|
|