error.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. """create errno-specific classes for IO or os calls."""
  2. from __future__ import annotations
  3. import errno
  4. import os
  5. import sys
  6. from typing import Callable
  7. from typing import TYPE_CHECKING
  8. from typing import TypeVar
  9. if TYPE_CHECKING:
  10. from typing_extensions import ParamSpec
  11. P = ParamSpec("P")
  12. R = TypeVar("R")
  13. class Error(EnvironmentError):
  14. def __repr__(self) -> str:
  15. return "{}.{} {!r}: {} ".format(
  16. self.__class__.__module__,
  17. self.__class__.__name__,
  18. self.__class__.__doc__,
  19. " ".join(map(str, self.args)),
  20. # repr(self.args)
  21. )
  22. def __str__(self) -> str:
  23. s = "[{}]: {}".format(
  24. self.__class__.__doc__,
  25. " ".join(map(str, self.args)),
  26. )
  27. return s
  28. _winerrnomap = {
  29. 2: errno.ENOENT,
  30. 3: errno.ENOENT,
  31. 17: errno.EEXIST,
  32. 18: errno.EXDEV,
  33. 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable
  34. 22: errno.ENOTDIR,
  35. 20: errno.ENOTDIR,
  36. 267: errno.ENOTDIR,
  37. 5: errno.EACCES, # anything better?
  38. }
  39. class ErrorMaker:
  40. """lazily provides Exception classes for each possible POSIX errno
  41. (as defined per the 'errno' module). All such instances
  42. subclass EnvironmentError.
  43. """
  44. _errno2class: dict[int, type[Error]] = {}
  45. def __getattr__(self, name: str) -> type[Error]:
  46. if name[0] == "_":
  47. raise AttributeError(name)
  48. eno = getattr(errno, name)
  49. cls = self._geterrnoclass(eno)
  50. setattr(self, name, cls)
  51. return cls
  52. def _geterrnoclass(self, eno: int) -> type[Error]:
  53. try:
  54. return self._errno2class[eno]
  55. except KeyError:
  56. clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,))
  57. errorcls = type(
  58. clsname,
  59. (Error,),
  60. {"__module__": "py.error", "__doc__": os.strerror(eno)},
  61. )
  62. self._errno2class[eno] = errorcls
  63. return errorcls
  64. def checked_call(
  65. self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
  66. ) -> R:
  67. """Call a function and raise an errno-exception if applicable."""
  68. __tracebackhide__ = True
  69. try:
  70. return func(*args, **kwargs)
  71. except Error:
  72. raise
  73. except OSError as value:
  74. if not hasattr(value, "errno"):
  75. raise
  76. errno = value.errno
  77. if sys.platform == "win32":
  78. try:
  79. cls = self._geterrnoclass(_winerrnomap[errno])
  80. except KeyError:
  81. raise value
  82. else:
  83. # we are not on Windows, or we got a proper OSError
  84. cls = self._geterrnoclass(errno)
  85. raise cls(f"{func.__name__}{args!r}")
  86. _error_maker = ErrorMaker()
  87. checked_call = _error_maker.checked_call
  88. def __getattr__(attr: str) -> type[Error]:
  89. return getattr(_error_maker, attr) # type: ignore[no-any-return]