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

46 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-14 02:11 +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, reason: __.Absential[ str | Exception ] = __.absent 

59 ) -> None: 

60 if __.is_absent( reason ): message = 'Invalid configuration.' 

61 else: message = f"Invalid configuration: {reason}" 

62 super( ).__init__( message ) 

63 

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

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

66 

67 

68class DataInvalidity( ConfigurationInvalidity ): 

69 ''' Data file content is invalid. ''' 

70 

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

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

73 

74 

75class FileOperationFailure( Omnierror, OSError ): 

76 ''' File or directory operation failure. ''' 

77 

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

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

80 super( ).__init__( message ) 

81 

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

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

84 

85 

86class ValidationCommandFailure( Omnierror ): 

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

88 

89 def __init__( 

90 self, 

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

92 returncode: int, 

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

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

95 ) -> None: 

96 self.command = command 

97 self.returncode = returncode 

98 self._temp_directory = temp_directory 

99 self._stderr = stderr 

100 message = ( 

101 f"Validation command failed with exit code " 

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

103 ) 

104 if not __.is_absent( temp_directory ): 

105 message = ( 

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

107 f"{temp_directory}" 

108 ) 

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

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

111 super( ).__init__( message ) 

112 

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

114 lines = [ 

115 f"\u274c Validation command failed " 

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

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

118 ] 

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

120 lines.append( self._stderr ) 

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

122 lines.append( 

123 f"\U0001f4c1 Temporary directory preserved at: " 

124 f"{self._temp_directory}" 

125 ) 

126 return tuple( lines )