Coverage for sources/agentsmgr/exceptions.py: 37%
116 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-26 02:00 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-26 02:00 +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 DataSourceInvalidity( Omnierror, ValueError ):
123 ''' Data source structure invalidity. '''
125 def __init__(
126 self, location: __.Path, missing_directories: tuple[ str, ... ]
127 ) -> None:
128 self.location = location
129 self.missing_directories = missing_directories
130 directories_list = ", ".join( missing_directories )
131 message = (
132 f"Invalid data source structure at {location}: "
133 f"missing required directories: {directories_list}" )
134 super( ).__init__( message )
136 def render_as_markdown( self ) -> tuple[ str, ... ]:
137 ''' Renders data source invalidity with helpful guidance. '''
138 lines = [ "## Error: Invalid Data Source Structure" ]
139 lines.append( "" )
140 lines.append(
141 "The data source location does not contain the expected "
142 "directory structure:" )
143 lines.append( "" )
144 lines.append( f" {self.location}" )
145 lines.append( "" )
146 lines.append( "**Missing required directories:**" )
147 lines.extend(
148 f"- `{directory}`" for directory in self.missing_directories )
149 lines.append( "" )
150 lines.append(
151 "Data sources should contain structured directories for "
152 "configurations, contents, and templates." )
153 return tuple( lines )
156class DataSourceNoSupport( Omnierror, ValueError ):
157 ''' Unsupported data source format error. '''
159 def __init__( self, source_spec: str ):
160 message = f"Unsupported source format: {source_spec}"
161 super( ).__init__( message )
166class GlobalsPopulationFailure( Omnierror, OSError ):
167 ''' Global settings population failure. '''
169 def __init__( self, source: __.Path, target: __.Path ):
170 message = f"Failed to populate global file from {source} to {target}"
171 super( ).__init__( message )
175class MemoryFileAbsence( Omnierror, FileNotFoundError ):
176 ''' Memory file absence.
178 Raised when project memory file (conventions.md) does not exist
179 but memory symlinks need to be created.
180 '''
182 def __init__( self, location: __.Path ) -> None:
183 self.location = location
184 super( ).__init__( f"Memory file not found: {location}" )
186 def render_as_markdown( self ) -> tuple[ str, ... ]:
187 ''' Renders memory file absence with helpful guidance. '''
188 lines = [ "## Error: Memory File Not Found" ]
189 lines.append( "" )
190 lines.append(
191 "The project memory file does not exist at the expected "
192 "location:" )
193 lines.append( "" )
194 lines.append( f" {self.location}" )
195 lines.append( "" )
196 lines.append(
197 "Memory files provide project-specific conventions and "
198 "context to AI coding assistants. Create this file before "
199 "running `agentsmgr populate`." )
200 lines.append( "" )
201 lines.append(
202 "**Suggested action**: Create "
203 "`.auxiliary/configuration/conventions.md` with "
204 "project-specific conventions, or copy from a template "
205 "project." )
206 return tuple( lines )
209class TargetModeNoSupport( Omnierror, ValueError ):
210 ''' Targeting mode lack of support. '''
212 def __init__( self, coder: str, mode: str, reason: str = '' ):
213 self.coder = coder
214 self.mode = mode
215 self.reason = reason
216 message = (
217 f"The {coder} coder does not support {mode} targeting mode." )
218 if reason: message = f"{message} {reason}"
219 super( ).__init__( message )
221 def render_as_markdown( self ) -> tuple[ str, ... ]:
222 ''' Renders targeting mode error with helpful guidance. '''
223 lines = [
224 "## Error: Unsupported Targeting Mode",
225 "",
226 f"The **{self.coder}** coder does not support "
227 f"**{self.mode}** targeting mode.",
228 ]
229 if self.reason:
230 lines.extend( [ "", self.reason ] )
231 return tuple( lines )
234class TemplateError( Omnierror, ValueError ):
235 ''' Template processing error. '''
237 def __init__( self, template_name: str ):
238 super( ).__init__( f"Template error: {template_name}" )
240 @classmethod
241 def for_missing_template(
242 cls, coder: str, item_type: str
243 ) -> __.typx.Self:
244 ''' Creates error for missing template. '''
245 return cls( f"no {item_type} template found for {coder}" )
247 @classmethod
248 def for_extension_parse( cls, template_name: str ) -> __.typx.Self:
249 ''' Creates error for extension parsing failure. '''
250 return cls( f"cannot determine output extension for {template_name}" )
253class ToolSpecificationInvalidity( ConfigurationInvalidity ):
254 ''' Tool specification invalidity. '''
256 def __init__( self, specification: __.typx.Any ):
257 message = f"Unrecognized tool specification: {specification}"
258 super( ).__init__( message )
261class ToolSpecificationTypeInvalidity( ConfigurationInvalidity ):
262 ''' Tool specification type invalidity. '''
264 def __init__( self, specification: __.typx.Any ):
265 specification_type = type( specification ).__name__
266 message = (
267 f"Tool specification must be string or dict, got: "
268 f"{specification_type}" )
269 super( ).__init__( message )