1"""create errno-specific classes for IO or os calls."""

2 from __future__ import annotations

3

4 import errno

5 import os

6 import sys

7 from typing import Callable

8 from typing import TYPE_CHECKING

9 from typing import TypeVar

10

11 if TYPE_CHECKING:

12 from typing_extensions import ParamSpec

13

14 P = ParamSpec("P")

15

16 R = TypeVar("R")

17

18

19 class Error(EnvironmentError):

20 def __repr__(self) -> str:

21 return "{}.{} {!r}: {} ".format(

22 self.__class__.__module__,

23 self.__class__.__name__,

24 self.__class__.__doc__,

25 " ".join(map(str, self.args)),

26 # repr(self.args)

27 )

28

29 def __str__(self) -> str:

30 s = "[{}]: {}".format(

31 self.__class__.__doc__,

32 " ".join(map(str, self.args)),

33 )

34 return s

35

36

37_winerrnomap = {

38 2: errno.ENOENT,

39 3: errno.ENOENT,

40 17: errno.EEXIST,

41 18: errno.EXDEV,

42 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable

43 22: errno.ENOTDIR,

44 20: errno.ENOTDIR,

45 267: errno.ENOTDIR,

46 5: errno.EACCES, # anything better?

47}

48

49

50 class ErrorMaker:

51 """lazily provides Exception classes for each possible POSIX errno

52 (as defined per the 'errno' module). All such instances

53 subclass EnvironmentError.

54 """

55

56 _errno2class: dict[int, type[Error]] = {}

57

58 def __getattr__(self, name: str) -> type[Error]:

59 if name[0] == "_":

60 raise AttributeError(name)

61 eno = getattr(errno, name)

62 cls = self._geterrnoclass(eno)

63 setattr(self, name, cls)

64 return cls

65

66 def _geterrnoclass(self, eno: int) -> type[Error]:

67 try:

68 return self._errno2class[eno]

69 except KeyError:

70 clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,))

71 errorcls = type(

72 clsname,

73 (Error,),

74 {"__module__": "py.error", "__doc__": os.strerror(eno)},

75 )

76 self._errno2class[eno] = errorcls

77 return errorcls

78

79 def checked_call(

80 self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs

81 ) -> R:

82 """Call a function and raise an errno-exception if applicable."""

83 __tracebackhide__ = True

84 try:

85 return func(*args, **kwargs)

86 except Error:

87 raise

88 except OSError as value:

89 if not hasattr(value, "errno"):

90 raise

91 errno = value.errno

92 if sys.platform == "win32":

93 try:

94 cls = self._geterrnoclass(_winerrnomap[errno])

95 except KeyError:

96 raise value

97 else:

98 # we are not on Windows, or we got a proper OSError

99 cls = self._geterrnoclass(errno)

100

101 raise cls(f"{func.__name__}{args!r}")

102

103

104_error_maker = ErrorMaker()

105 checked_call = _error_maker.checked_call

106

107

108 def __getattr__(attr: str) -> type[Error]:

109 return getattr(_error_maker, attr) # type: ignore[no-any-return]