Coverage for sources / agentsmgr / exceptions.py: 38%
124 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 21:55 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 21:55 +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 CoderResourceAbsence( Omnierror, FileNotFoundError ):
48 ''' Coder resource absence. '''
50 def __init__( self, coder: str, path: __.Path ):
51 message = f"Resources for {coder} not found at {path}"
52 super( ).__init__( message )
55class CoderResourceCopyFailure( Omnierror, OSError ):
56 ''' Coder resource copy failure. '''
58 def __init__( self, source: __.Path, target: __.Path ):
59 message = f"Failed to copy resources from {source} to {target}"
60 super( ).__init__( message )
63class ConfigurationAbsence( Omnierror, FileNotFoundError ):
65 def __init__(
66 self, location: __.Absential[ __.Path ] = __.absent
67 ) -> None:
68 message = "Could not locate agents configuration"
69 if not __.is_absent( location ):
70 message = f"{message} at '{location}'"
71 super( ).__init__( f"{message}." )
73 def render_as_markdown( self ) -> tuple[ str, ... ]:
74 return (
75 f"❌ {self}",
76 "",
77 "Run 'copier copy gh:emcd/agents-common' to configure agents."
78 )
81class ConfigurationInvalidity( Omnierror, ValueError ):
82 ''' Base configuration data invalidity. '''
84 def __init__( self, reason: __.Absential[ str | Exception ] = __.absent ):
85 if __.is_absent( reason ): message = "Invalid configuration."
86 else: message = f"Invalid configuration: {reason}"
87 super( ).__init__( message )
90class ContentAbsence( Omnierror, FileNotFoundError ):
91 ''' Content file absence. '''
93 def __init__( self, content_type: str, content_name: str, coder: str ):
94 message = (
95 f"No {content_type} content found for {coder}: {content_name}" )
96 super( ).__init__( message )
99class FileOperationFailure( Omnierror, OSError ):
100 ''' File or directory operation failure. '''
102 def __init__( self, path: __.Path, operation: str = "access file" ):
103 message = f"Failed to {operation}: {path}"
104 super( ).__init__( message )
107class InstructionSourceInvalidity( Omnierror, ValueError ):
108 ''' Instruction source configuration invalidity. '''
111class InstructionSourceFieldAbsence( InstructionSourceInvalidity ):
112 ''' Instruction source 'source' field absence. '''
114 def __init__( self ):
115 message = "Instruction source missing required 'source' field."
116 super( ).__init__( message )
119class InstructionFilesConfigurationInvalidity(
120 InstructionSourceInvalidity
121):
122 ''' Instruction files configuration format invalidity. '''
124 def __init__( self ):
125 message = "Instruction 'files' configuration must be a mapping."
126 super( ).__init__( message )
129class ContextInvalidity( Omnierror, TypeError ):
130 ''' Invalid execution context. '''
132 def __init__( self ):
133 message = "Invalid execution context: expected agentsmgr.cli.Globals"
134 super( ).__init__( message )
137class DataSourceInvalidity( Omnierror, ValueError ):
138 ''' Data source structure invalidity. '''
140 def __init__(
141 self, location: __.Path, missing_directories: tuple[ str, ... ]
142 ) -> None:
143 self.location = location
144 self.missing_directories = missing_directories
145 directories_list = ", ".join( missing_directories )
146 message = (
147 f"Invalid data source structure at {location}: "
148 f"missing required directories: {directories_list}" )
149 super( ).__init__( message )
151 def render_as_markdown( self ) -> tuple[ str, ... ]:
152 ''' Renders data source invalidity with helpful guidance. '''
153 lines = [ "## Error: Invalid Data Source Structure" ]
154 lines.append( "" )
155 lines.append(
156 "The data source location does not contain the expected "
157 "directory structure:" )
158 lines.append( "" )
159 lines.append( f" {self.location}" )
160 lines.append( "" )
161 lines.append( "**Missing required directories:**" )
162 lines.extend(
163 f"- `{directory}`" for directory in self.missing_directories )
164 lines.append( "" )
165 lines.append(
166 "Data sources should contain structured directories for "
167 "configurations, contents, and templates." )
168 return tuple( lines )
171class DataSourceNoSupport( Omnierror, ValueError ):
172 ''' Unsupported data source format error. '''
174 def __init__( self, source_spec: str ):
175 message = f"Unsupported source format: {source_spec}"
176 super( ).__init__( message )
179class GlobalsPopulationFailure( Omnierror, OSError ):
180 ''' Global settings population failure. '''
182 def __init__( self, source: __.Path, target: __.Path ):
183 message = f"Failed to populate global file from {source} to {target}"
184 super( ).__init__( message )
187class MemoryFileAbsence( Omnierror, FileNotFoundError ):
188 ''' Memory file absence.
190 Raised when project memory file (AGENTS.md) does not exist
191 but memory symlinks need to be created.
192 '''
194 def __init__( self, location: __.Path ) -> None:
195 self.location = location
196 super( ).__init__( f"Memory file not found: {location}" )
198 def render_as_markdown( self ) -> tuple[ str, ... ]:
199 ''' Renders memory file absence with helpful guidance. '''
200 lines = [ "## Error: Memory File Not Found" ]
201 lines.append( "" )
202 lines.append(
203 "The project memory file does not exist at the expected "
204 "location:" )
205 lines.append( "" )
206 lines.append( f" {self.location}" )
207 lines.append( "" )
208 lines.append(
209 "Memory files provide project-specific conventions and "
210 "context to AI coding assistants. Create this file before "
211 "running `agentsmgr populate`." )
212 lines.append( "" )
213 lines.append(
214 "**Suggested action**: Create "
215 "`.auxiliary/configuration/AGENTS.md` with "
216 "project-specific conventions, or copy from a template "
217 "project." )
218 return tuple( lines )
221class TargetModeNoSupport( Omnierror, ValueError ):
222 ''' Targeting mode lack of support. '''
224 def __init__( self, coder: str, mode: str, reason: str = '' ):
225 self.coder = coder
226 self.mode = mode
227 self.reason = reason
228 message = (
229 f"The {coder} coder does not support {mode} targeting mode." )
230 if reason: message = f"{message} {reason}"
231 super( ).__init__( message )
233 def render_as_markdown( self ) -> tuple[ str, ... ]:
234 ''' Renders targeting mode error with helpful guidance. '''
235 lines = [
236 "## Error: Unsupported Targeting Mode",
237 "",
238 f"The **{self.coder}** coder does not support "
239 f"**{self.mode}** targeting mode.",
240 ]
241 if self.reason:
242 lines.extend( [ "", self.reason ] )
243 return tuple( lines )
246class TemplateError( Omnierror, ValueError ):
247 ''' Template processing error. '''
249 def __init__( self, template_name: str ):
250 super( ).__init__( f"Template error: {template_name}" )
252 @classmethod
253 def for_missing_template(
254 cls, coder: str, item_type: str
255 ) -> __.typx.Self:
256 ''' Creates error for missing template. '''
257 return cls( f"no {item_type} template found for {coder}" )
259 @classmethod
260 def for_extension_parse( cls, template_name: str ) -> __.typx.Self:
261 ''' Creates error for extension parsing failure. '''
262 return cls( f"cannot determine output extension for {template_name}" )
265class ToolSpecificationInvalidity( ConfigurationInvalidity ):
266 ''' Tool specification invalidity. '''
268 def __init__( self, specification: __.typx.Any ):
269 message = f"Unrecognized tool specification: {specification}"
270 super( ).__init__( message )
273class ToolSpecificationTypeInvalidity( ConfigurationInvalidity ):
274 ''' Tool specification type invalidity. '''
276 def __init__( self, specification: __.typx.Any ):
277 specification_type = type( specification ).__name__
278 message = (
279 f"Tool specification must be string or dict, got: "
280 f"{specification_type}" )
281 super( ).__init__( message )