1"""Convenience layer on top of stdlib's shutil and os"""
2
3import os
4import stat
5from typing import Callable, TypeVar
6
7from .compat import py311
8
9from distutils import log
10
11try:
12 from os import chmod # pyright: ignore[reportAssignmentType]
13 # Losing type-safety w/ pyright, but that's ok
14except ImportError: # pragma: no cover
15 # Jython compatibility
16 def chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy reuses the imported definition anyway
17 pass
18
19
20_T = TypeVar("_T")
21
22
23def attempt_chmod_verbose(path, mode):
24 log.debug("changing mode of %s to %o", path, mode)
25 try:
26 chmod(path, mode)
27 except OSError as e: # pragma: no cover
28 log.debug("chmod failed: %s", e)
29
30
31# Must match shutil._OnExcCallback
32def _auto_chmod(
33 func: Callable[..., _T], arg: str, exc: BaseException
34) -> _T: # pragma: no cover
35 """shutils onexc callback to automatically call chmod for certain functions."""
36 # Only retry for scenarios known to have an issue
37 if func in [os.unlink, os.remove] and os.name == 'nt':
38 attempt_chmod_verbose(arg, stat.S_IWRITE)
39 return func(arg)
40 raise exc
41
42
43def rmtree(path, ignore_errors=False, onexc=_auto_chmod):
44 """
45 Similar to ``shutil.rmtree`` but automatically executes ``chmod``
46 for well know Windows failure scenarios.
47 """
48 return py311.shutil_rmtree(path, ignore_errors, onexc)
49
50
51def rmdir(path, **opts):
52 if os.path.isdir(path):
53 rmtree(path, **opts)
54
55
56def current_umask():
57 tmp = os.umask(0o022)
58 os.umask(tmp)
59 return tmp