Coverage for sources/agentsmgr/exceptions.py: 43%
116 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-15 21:08 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-15 21:08 +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." 69 ↛ 71line 69 didn't jump to line 71 because
70 else: message = f"Invalid configuration: {reason}"
71 super( ).__init__( message )
74class ContentAbsence( Omnierror, FileNotFoundError ):
75 ''' Content file absence. '''
77 def __init__( self, content_type: str, content_name: str, coder: str ):
78 message = (
79 f"No {content_type} content found for {coder}: {content_name}" )
80 super( ).__init__( message )
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 )
91class InstructionSourceInvalidity( Omnierror, ValueError ):
92 ''' Instruction source configuration invalidity. '''
95class InstructionSourceFieldAbsence( InstructionSourceInvalidity ):
96 ''' Instruction source 'source' field absence. '''
98 def __init__( self ):
99 message = "Instruction source missing required 'source' field."
100 super( ).__init__( message )
103class InstructionFilesConfigurationInvalidity(
104 InstructionSourceInvalidity
105):
106 ''' Instruction files configuration format invalidity. '''
108 def __init__( self ):
109 message = "Instruction 'files' configuration must be a mapping."
110 super( ).__init__( message )
113class ContextInvalidity( Omnierror, TypeError ):
114 ''' Invalid execution context. '''
116 def __init__( self ):
117 message = "Invalid execution context: expected agentsmgr.cli.Globals"
118 super( ).__init__( message )
121class DataSourceInvalidity( Omnierror, ValueError ):
122 ''' Data source structure invalidity. '''
124 def __init__(
125 self, location: __.Path, missing_directories: tuple[ str, ... ]
126 ) -> None:
127 self.location = location
128 self.missing_directories = missing_directories
129 directories_list = ", ".join( missing_directories )
130 message = (
131 f"Invalid data source structure at {location}: "
132 f"missing required directories: {directories_list}" )
133 super( ).__init__( message )
135 def render_as_markdown( self ) -> tuple[ str, ... ]:
136 ''' Renders data source invalidity with helpful guidance. '''
137 lines = [ "## Error: Invalid Data Source Structure" ]
138 lines.append( "" )
139 lines.append(
140 "The data source location does not contain the expected "
141 "directory structure:" )
142 lines.append( "" )
143 lines.append( f" {self.location}" )
144 lines.append( "" )
145 lines.append( "**Missing required directories:**" )
146 lines.extend(
147 f"- `{directory}`" for directory in self.missing_directories )
148 lines.append( "" )
149 lines.append(
150 "Data sources should contain structured directories for "
151 "configurations, contents, and templates." )
152 return tuple( lines )
155class DataSourceNoSupport( Omnierror, ValueError ):
156 ''' Unsupported data source format error. '''
158 def __init__( self, source_spec: str ):
159 message = f"Unsupported source format: {source_spec}"
160 super( ).__init__( message )
163class GlobalsPopulationFailure( Omnierror, OSError ):
164 ''' Global settings population failure. '''
166 def __init__( self, source: __.Path, target: __.Path ):
167 message = f"Failed to populate global file from {source} to {target}"
168 super( ).__init__( message )
171class MemoryFileAbsence( Omnierror, FileNotFoundError ):
172 ''' Memory file absence.
174 Raised when project memory file (AGENTS.md) does not exist
175 but memory symlinks need to be created.
176 '''
178 def __init__( self, location: __.Path ) -> None:
179 self.location = location
180 super( ).__init__( f"Memory file not found: {location}" )
182 def render_as_markdown( self ) -> tuple[ str, ... ]:
183 ''' Renders memory file absence with helpful guidance. '''
184 lines = [ "## Error: Memory File Not Found" ]
185 lines.append( "" )
186 lines.append(
187 "The project memory file does not exist at the expected "
188 "location:" )
189 lines.append( "" )
190 lines.append( f" {self.location}" )
191 lines.append( "" )
192 lines.append(
193 "Memory files provide project-specific conventions and "
194 "context to AI coding assistants. Create this file before "
195 "running `agentsmgr populate`." )
196 lines.append( "" )
197 lines.append(
198 "**Suggested action**: Create "
199 "`.auxiliary/agents/agents.md` with "
200 "project-specific conventions, or copy from a template "
201 "project." )
202 return tuple( lines )
205class TargetModeNoSupport( Omnierror, ValueError ):
206 ''' Targeting mode lack of support. '''
208 def __init__( self, coder: str, mode: str, reason: str = '' ):
209 self.coder = coder
210 self.mode = mode
211 self.reason = reason
212 message = (
213 f"The {coder} coder does not support {mode} targeting mode." )
214 if reason: message = f"{message} {reason}"
215 super( ).__init__( message )
217 def render_as_markdown( self ) -> tuple[ str, ... ]:
218 ''' Renders targeting mode error with helpful guidance. '''
219 lines = [
220 "## Error: Unsupported Targeting Mode",
221 "",
222 f"The **{self.coder}** coder does not support "
223 f"**{self.mode}** targeting mode.",
224 ]
225 if self.reason:
226 lines.extend( [ "", self.reason ] )
227 return tuple( lines )
230class TemplateError( Omnierror, ValueError ):
231 ''' Template processing error. '''
233 def __init__( self, template_name: str ):
234 super( ).__init__( f"Template error: {template_name}" )
236 @classmethod
237 def for_missing_template(
238 cls, coder: str, item_type: str
239 ) -> __.typx.Self:
240 ''' Creates error for missing template. '''
241 return cls( f"no {item_type} template found for {coder}" )
243 @classmethod
244 def for_extension_parse( cls, template_name: str ) -> __.typx.Self:
245 ''' Creates error for extension parsing failure. '''
246 return cls( f"cannot determine output extension for {template_name}" )
249class ToolSpecificationInvalidity( ConfigurationInvalidity ):
250 ''' Tool specification invalidity. '''
252 def __init__( self, specification: __.typx.Any ):
253 message = f"Unrecognized tool specification: {specification}"
254 super( ).__init__( message )
257class ToolSpecificationTypeInvalidity( ConfigurationInvalidity ):
258 ''' Tool specification type invalidity. '''
260 def __init__( self, specification: __.typx.Any ):
261 specification_type = type( specification ).__name__
262 message = (
263 f"Tool specification must be string or dict, got: "
264 f"{specification_type}" )
265 super( ).__init__( message )