Coverage for src/mactime/errors.py: 100%

53 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-24 19:31 +0100

1from __future__ import annotations 

2 

3import ctypes 

4import errno 

5import os 

6from typing import Optional, Type, ClassVar 

7 

8 

9class MacTimeError(Exception): 

10 """Base exception class for mactime errors.""" 

11 

12 exit_code: ClassVar[int] = 1 

13 

14 

15class ArgumentsError(MacTimeError, ValueError): 

16 exit_code = 2 

17 

18 

19class NotEnoughArgumentsError(MacTimeError, ValueError): 

20 exit_code = 22 

21 

22 

23class FSOperationError(MacTimeError, OSError): 

24 """Base class for file operation errors.""" 

25 

26 _registry: ClassVar[dict[int, Type[FSOperationError]]] = {} 

27 error_codes: ClassVar[set[int]] = set() 

28 error_message: ClassVar[str] = "Unknown error" 

29 exit_code: ClassVar[int] = 22 

30 

31 def __init__( 

32 self, 

33 path: str, 

34 operation: str, 

35 errno: int, 

36 message: str | None = None, 

37 ): 

38 self.path = path 

39 self.operation = operation 

40 self.errno = errno 

41 self.message = message or self.error_message 

42 super().__init__(f"{operation} on {path!r}: {self.message} (errno {errno!r})") 

43 

44 def __init_subclass__(cls) -> None: 

45 super().__init_subclass__() 

46 for code in cls.error_codes: 

47 FSOperationError._registry[code] = cls 

48 

49 @classmethod 

50 def check_call(cls, ret: int, path: str | os.PathLike, operation: str) -> None: 

51 if ret == 0: 

52 return 

53 

54 err = ctypes.get_errno() 

55 specific_error = cls._registry.get(err, FSOperationError) 

56 raise specific_error(path, operation, err) 

57 

58 

59class PathNotFoundError(FSOperationError): 

60 error_codes = {errno.ENOENT} 

61 error_message = "Path not found" 

62 exit_code = 2 

63 

64 

65class FSPermissionError(FSOperationError): 

66 error_codes = {errno.EACCES, errno.EPERM} 

67 error_message = "Permission denied" 

68 exit_code = 13 

69 

70 

71class InvalidAttributeError(FSOperationError): 

72 error_codes = {errno.EINVAL} 

73 error_message = "Invalid attribute or operation" 

74 exit_code = 22 

75 

76 

77class UnsupportedOperationError(FSOperationError): 

78 error_codes = {errno.ENOTSUP} 

79 error_message = "Operation not supported" 

80 exit_code = 45 

81 

82 

83class FileIOError(FSOperationError): 

84 error_codes = {errno.EIO} 

85 error_message = "I/O error occurred" 

86 exit_code = 5