Coverage for sources/copiertv/exceptions.py: 100%

49 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-10 00:24 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Family of exceptions for package API. ''' 

22 

23 

24from . import __ 

25 

26 

27class Omniexception( __.immut.exceptions.Omniexception ): 

28 ''' Base for all exceptions raised by package API. ''' 

29 

30 

31class Omnierror( Omniexception, Exception ): 

32 ''' Base for error exceptions raised by package API. ''' 

33 

34 

35class ConfigurationAbsence( Omnierror, FileNotFoundError ): 

36 ''' Required configuration resource not found. ''' 

37 

38 def __init__( 

39 self, location: __.Absential[ __.Path ] = __.absent 

40 ) -> None: 

41 message = 'Could not locate configuration' 

42 if not __.is_absent( location ): 

43 message = f"{message} at '{location}'" 

44 super( ).__init__( f"{message}." ) 

45 

46 def render_as_markdown( self ) -> tuple[ str, ... ]: 

47 return ( 

48 f"\u274c {self}", 

49 '', 

50 'Ensure the answers directory and copier.yaml exist.', 

51 ) 

52 

53 

54class ConfigurationInvalidity( Omnierror, ValueError ): 

55 ''' Configuration data is invalid or a dependency is missing. ''' 

56 

57 def __init__( 

58 self, 

59 reason: __.Absential[ str | Exception ] = __.absent, 

60 *, 

61 subject: __.Absential[ str ] = __.absent, 

62 ) -> None: 

63 if __.is_absent( reason ): 

64 if __.is_absent( subject ): 

65 message = 'Invalid configuration.' 

66 else: 

67 message = f"Missing required configuration: {subject}." 

68 else: 

69 message = f"Invalid configuration: {reason}" 

70 super( ).__init__( message ) 

71 

72 def render_as_markdown( self ) -> tuple[ str, ... ]: 

73 return ( f"\u274c {self}", ) 

74 

75 

76class DataInvalidity( ConfigurationInvalidity ): 

77 ''' Data file content is invalid. ''' 

78 

79 def __init__( self, path: __.Path, cause: str ) -> None: 

80 super( ).__init__( f"{cause}: {path}" ) 

81 

82 

83class FileOperationFailure( Omnierror, OSError ): 

84 ''' File or directory operation failure. ''' 

85 

86 def __init__( self, path: __.Path, operation: str = 'access file' ): 

87 message = f"Failed to {operation}: {path}" 

88 super( ).__init__( message ) 

89 

90 def render_as_markdown( self ) -> tuple[ str, ... ]: 

91 return ( f"\u274c {self}", ) 

92 

93 

94class ValidationCommandFailure( Omnierror ): 

95 ''' Validation command exited with non-zero status. ''' 

96 

97 def __init__( 

98 self, 

99 command: tuple[ str, ... ], 

100 returncode: int, 

101 temp_directory: __.Absential[ __.Path ] = __.absent, 

102 stderr: __.Absential[ str ] = __.absent, 

103 ) -> None: 

104 self.command = command 

105 self.returncode = returncode 

106 self._temp_directory = temp_directory 

107 self._stderr = stderr 

108 message = ( 

109 f"Validation command failed with exit code " 

110 f"{returncode}: {' '.join( command )}" 

111 ) 

112 if not __.is_absent( temp_directory ): 

113 message = ( 

114 f"{message}\nTemporary directory preserved at: " 

115 f"{temp_directory}" 

116 ) 

117 if not __.is_absent( stderr ) and stderr: 

118 message = f"{message}\n{stderr}" 

119 super( ).__init__( message ) 

120 

121 def render_as_markdown( self ) -> tuple[ str, ... ]: 

122 lines = [ 

123 f"\u274c Validation command failed " 

124 f"(exit code {self.returncode}): " 

125 f"{' '.join( self.command )}", 

126 ] 

127 if not __.is_absent( self._stderr ) and self._stderr: 

128 lines.append( self._stderr ) 

129 if not __.is_absent( self._temp_directory ): 

130 lines.append( 

131 f"\U0001f4c1 Temporary directory preserved at: " 

132 f"{self._temp_directory}" 

133 ) 

134 return tuple( lines )