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
« 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 -*-
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#============================================================================#
21''' Family of exceptions for package API. '''
24from . import __
27class Omniexception( __.immut.exceptions.Omniexception ):
28 ''' Base for all exceptions raised by package API. '''
31class Omnierror( Omniexception, Exception ):
32 ''' Base for error exceptions raised by package API. '''
35class ConfigurationAbsence( Omnierror, FileNotFoundError ):
36 ''' Required configuration resource not found. '''
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}." )
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 )
54class ConfigurationInvalidity( Omnierror, ValueError ):
55 ''' Configuration data is invalid or a dependency is missing. '''
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 )
72 def render_as_markdown( self ) -> tuple[ str, ... ]:
73 return ( f"\u274c {self}", )
76class DataInvalidity( ConfigurationInvalidity ):
77 ''' Data file content is invalid. '''
79 def __init__( self, path: __.Path, cause: str ) -> None:
80 super( ).__init__( f"{cause}: {path}" )
83class FileOperationFailure( Omnierror, OSError ):
84 ''' File or directory operation failure. '''
86 def __init__( self, path: __.Path, operation: str = 'access file' ):
87 message = f"Failed to {operation}: {path}"
88 super( ).__init__( message )
90 def render_as_markdown( self ) -> tuple[ str, ... ]:
91 return ( f"\u274c {self}", )
94class ValidationCommandFailure( Omnierror ):
95 ''' Validation command exited with non-zero status. '''
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 )
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 )