Coverage for sources/appcore/exceptions.py: 67%
43 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 00:58 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 00:58 +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.
23 This module defines a comprehensive exception hierarchy for the appcore
24 library, providing specific exception types for different failure modes
25 while maintaining a consistent interface for error handling and debugging.
27 Exception Hierarchy
28 ===================
30 The exception hierarchy follows a two-tier design:
32 * :class:`Omniexception` - Base for all package exceptions
33 * :class:`Omnierror` - Base for error exceptions, inherits from both
34 ``Omniexception`` and ``Exception``
36 Usage
37 =====
39 Catch all package errors with :class:`Omnierror` or with built-in exception
40 types. See class inheritance for details.
41'''
44from . import __
47class Omniexception( __.immut.exceptions.Omniexception ):
48 ''' Base for all exceptions raised by package API. '''
50 def render_dictionary( self ) -> dict[ str, __.typx.Any ]:
51 ''' Returns dictionary representation of exception. '''
52 return render_dictionary( self )
54 def render_json( self, compact: bool = False, indent: int = 2 ) -> str:
55 ''' Renders exception as JSON into string. '''
56 dictionary = self.render_dictionary( )
57 from json import dumps
58 if compact:
59 return dumps(
60 dictionary, ensure_ascii = False, separators = ( ',', ':' ) )
61 return dumps( dictionary, ensure_ascii = False, indent = indent )
63 def render_markdown( self ) -> tuple[ str, ... ]:
64 # TODO: Properly handle exception groups.
65 # TODO: Optionally handle tracebacks.
66 ''' Renders exception as Markdown into sequence of lines. '''
67 dictionary = self.render_dictionary( )
68 summary = "[**{fqclass}**] {message}".format(
69 fqclass = dictionary[ 'fqclass' ],
70 message = dictionary[ 'message' ] )
71 return ( summary, )
73 def render_toml( self ) -> str:
74 ''' Renders exception as TOML into string. '''
75 from tomli_w import dumps
76 return dumps( self.render_dictionary( ) )
79def render_dictionary( exception: BaseException ) -> dict[ str, __.typx.Any ]:
80 # TODO? Handle via third-party library.
81 # TODO: Properly handle exception groups.
82 # TODO: Optionally handle tracebacks.
83 ''' Returns dictionary representation of exception. '''
84 class_ = type( exception )
85 return {
86 'class': class_.__name__,
87 'fqclass': f"{class_.__module__}.{class_.__qualname__}",
88 'message': str( exception ), }
91class Omnierror( Omniexception, Exception ):
92 ''' Base for error exceptions raised by package API. '''
95class AddressLocateFailure( Omnierror, LookupError ):
96 ''' Failure to locate address. '''
98 def __init__(
99 self, subject: str, address: __.cabc.Sequence[ str ], part: str
100 ):
101 super( ).__init__(
102 f"Could not locate part '{part}' of address '{address}' "
103 f"in {subject}." )
106class AsyncAssertionFailure( Omnierror, AssertionError, TypeError ):
107 ''' Assertion of awaitability of entity failed. '''
109 def __init__( self, entity: __.typx.Any ):
110 super( ).__init__( f"Entity must be awaitable: {entity!r}" )
113class ContextInvalidity( Omnierror, TypeError, ValueError ):
115 def __init__( self, auxdata: __.typx.Any ):
116 # TODO: Add module name to fully-qualified name.
117 fqname = type( auxdata ).__qualname__
118 super( ).__init__( f"Invalid context object type: {fqname}" )
121class DependencyAbsence( Omnierror, ImportError ):
123 def __init__( self, dependency: str, feature: str ):
124 super( ).__init__(
125 f"Optional dependency {dependency!r} missing "
126 f"feature {feature!r}." )
129class EntryAssertionFailure( Omnierror, AssertionError, KeyError ):
130 ''' Assertion of entry in dictionary failed. '''
132 def __init__( self, subject: str, name: str ):
133 super( ).__init__( f"Could not find entry '{name}' in {subject}." )
136class FileLocateFailure( Omnierror, FileNotFoundError ):
137 ''' Failure to locate file. '''
139 def __init__( self, subject: str, name: str ):
140 super( ).__init__(
141 f"Could not locate file '{name}' for {subject}." )
144class OperationInvalidity( Omnierror, RuntimeError ):
145 ''' Invalid operation. '''
147 def __init__( self, subject: str, name: str ):
148 super( ).__init__(
149 f"Could not perform operation '{name}' on {subject}." )