Coverage for sources/agentsmgr/exceptions.py: 41%
97 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-24 01:49 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-24 01:49 +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. '''
34 def render_as_markdown( self ) -> tuple[ str, ... ]:
35 ''' Renders exception as Markdown lines for display. '''
36 return ( f"❌ {self}", )
39class CoderAbsence( Omnierror, ValueError ):
40 ''' Coder absence in registry. '''
42 def __init__( self, coder: str ):
43 message = f"Coder not found in registry: {coder}"
44 super( ).__init__( message )
47class ConfigurationAbsence( Omnierror, FileNotFoundError ):
49 def __init__(
50 self, location: __.Absential[ __.Path ] = __.absent
51 ) -> None:
52 message = "Could not locate agents configuration"
53 if not __.is_absent( location ):
54 message = f"{message} at '{location}'"
55 super( ).__init__( f"{message}." )
57 def render_as_markdown( self ) -> tuple[ str, ... ]:
58 return (
59 f"❌ {self}",
60 "",
61 "Run 'copier copy gh:emcd/agents-common' to configure agents."
62 )
65class ConfigurationInvalidity( Omnierror, ValueError ):
66 ''' Base configuration data invalidity. '''
68 def __init__( self, reason: __.Absential[ str | Exception ] = __.absent ):
69 if __.is_absent( reason ): message = "Invalid configuration."
70 else: message = f"Invalid configuration: {reason}"
71 super( ).__init__( message )
75class ContentAbsence( Omnierror, FileNotFoundError ):
76 ''' Content file absence. '''
78 def __init__( self, content_type: str, content_name: str, coder: str ):
79 message = (
80 f"No {content_type} content found for {coder}: {content_name}" )
81 super( ).__init__( message )
84class FileOperationFailure( Omnierror, OSError ):
85 ''' File or directory operation failure. '''
87 def __init__( self, path: __.Path, operation: str = "access file" ):
88 message = f"Failed to {operation}: {path}"
89 super( ).__init__( message )
92class InstructionSourceInvalidity( Omnierror, ValueError ):
93 ''' Instruction source configuration invalidity. '''
96class InstructionSourceFieldAbsence( InstructionSourceInvalidity ):
97 ''' Instruction source 'source' field absence. '''
99 def __init__( self ):
100 message = "Instruction source missing required 'source' field."
101 super( ).__init__( message )
104class InstructionFilesConfigurationInvalidity(
105 InstructionSourceInvalidity
106):
107 ''' Instruction files configuration format invalidity. '''
109 def __init__( self ):
110 message = "Instruction 'files' configuration must be a mapping."
111 super( ).__init__( message )
114class ContextInvalidity( Omnierror, TypeError ):
115 ''' Invalid execution context. '''
117 def __init__( self ):
118 message = "Invalid execution context: expected agentsmgr.cli.Globals"
119 super( ).__init__( message )
122class DataSourceNoSupport( Omnierror, ValueError ):
123 ''' Unsupported data source format error. '''
125 def __init__( self, source_spec: str ):
126 message = f"Unsupported source format: {source_spec}"
127 super( ).__init__( message )
132class GlobalsPopulationFailure( Omnierror, OSError ):
133 ''' Global settings population failure. '''
135 def __init__( self, source: __.Path, target: __.Path ):
136 message = f"Failed to populate global file from {source} to {target}"
137 super( ).__init__( message )
141class MemoryFileAbsence( Omnierror, FileNotFoundError ):
142 ''' Memory file absence.
144 Raised when project memory file (conventions.md) does not exist
145 but memory symlinks need to be created.
146 '''
148 def __init__( self, location: __.Path ) -> None:
149 self.location = location
150 super( ).__init__( f"Memory file not found: {location}" )
152 def render_as_markdown( self ) -> tuple[ str, ... ]:
153 ''' Renders memory file absence with helpful guidance. '''
154 lines = [ "## Error: Memory File Not Found" ]
155 lines.append( "" )
156 lines.append(
157 "The project memory file does not exist at the expected "
158 "location:" )
159 lines.append( "" )
160 lines.append( f" {self.location}" )
161 lines.append( "" )
162 lines.append(
163 "Memory files provide project-specific conventions and "
164 "context to AI coding assistants. Create this file before "
165 "running `agentsmgr populate`." )
166 lines.append( "" )
167 lines.append(
168 "**Suggested action**: Create "
169 "`.auxiliary/configuration/conventions.md` with "
170 "project-specific conventions, or copy from a template "
171 "project." )
172 return tuple( lines )
175class TargetModeNoSupport( Omnierror, ValueError ):
176 ''' Targeting mode lack of support. '''
178 def __init__( self, coder: str, mode: str, reason: str = '' ):
179 self.coder = coder
180 self.mode = mode
181 self.reason = reason
182 message = (
183 f"The {coder} coder does not support {mode} targeting mode." )
184 if reason: message = f"{message} {reason}"
185 super( ).__init__( message )
187 def render_as_markdown( self ) -> tuple[ str, ... ]:
188 ''' Renders targeting mode error with helpful guidance. '''
189 lines = [
190 "## Error: Unsupported Targeting Mode",
191 "",
192 f"The **{self.coder}** coder does not support "
193 f"**{self.mode}** targeting mode.",
194 ]
195 if self.reason:
196 lines.extend( [ "", self.reason ] )
197 return tuple( lines )
200class TemplateError( Omnierror, ValueError ):
201 ''' Template processing error. '''
203 def __init__( self, template_name: str ):
204 super( ).__init__( f"Template error: {template_name}" )
206 @classmethod
207 def for_missing_template(
208 cls, coder: str, item_type: str
209 ) -> __.typx.Self:
210 ''' Creates error for missing template. '''
211 return cls( f"no {item_type} template found for {coder}" )
213 @classmethod
214 def for_extension_parse( cls, template_name: str ) -> __.typx.Self:
215 ''' Creates error for extension parsing failure. '''
216 return cls( f"cannot determine output extension for {template_name}" )
219class ToolSpecificationInvalidity( ConfigurationInvalidity ):
220 ''' Tool specification invalidity. '''
222 def __init__( self, specification: __.typx.Any ):
223 message = f"Unrecognized tool specification: {specification}"
224 super( ).__init__( message )
227class ToolSpecificationTypeInvalidity( ConfigurationInvalidity ):
228 ''' Tool specification type invalidity. '''
230 def __init__( self, specification: __.typx.Any ):
231 specification_type = type( specification ).__name__
232 message = (
233 f"Tool specification must be string or dict, got: "
234 f"{specification_type}" )
235 super( ).__init__( message )