Coverage for sources/librovore/exceptions.py: 65%
127 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-17 23:43 +0000
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-17 23:43 +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. '''
24import urllib.parse as _urlparse
26from . import __
29class Omniexception( __.immut.Object, BaseException ):
30 ''' Base for all exceptions raised by package API. '''
32 _attribute_visibility_includes_: __.cabc.Collection[ str ] = (
33 frozenset( ( '__cause__', '__context__', ) ) )
36class Omnierror( Omniexception, Exception ):
37 ''' Base for error exceptions raised by package API. '''
40class DetectionConfidenceInvalidity( Omnierror, ValueError ):
41 ''' Detection confidence value is out of valid range. '''
43 def __init__( self, confidence: float ):
44 self.confidence = confidence
45 super( ).__init__( f"Confidence {confidence} not in range [0.0, 1.0]" )
48class DocumentationContentAbsence( Omnierror, ValueError ):
49 ''' Documentation main content container not found. '''
51 def __init__( self, url: str ):
52 message = f"No main content found in documentation at '{url}'."
53 self.url = url
54 super( ).__init__( message )
57class DocumentationInaccessibility( Omnierror, RuntimeError ):
58 ''' Documentation file or resource absent or inaccessible. '''
60 def __init__( self, url: str, cause: str | Exception ):
61 message = f"Documentation at '{url}' is inaccessible. Cause: {cause}"
62 self.url = url
63 super( ).__init__( message )
66class DocumentationObjectAbsence( Omnierror, ValueError ):
67 ''' Requested object not found in documentation page. '''
69 def __init__( self, object_id: str, url: str ):
70 message = (
71 f"Object '{object_id}' not found in documentation page "
72 f"at '{url}'" )
73 self.object_id = object_id
74 self.url = url
75 super( ).__init__( message )
78class DocumentationParseFailure( Omnierror, ValueError ):
79 ''' Documentation HTML parsing failed or content malformed. '''
81 def __init__( self, url: str, cause: str | Exception ):
82 message = f"Cannot parse documentation at '{url}'. Cause: {cause}"
83 self.url = url
84 super( ).__init__( message )
87class ExtensionCacheFailure( Omnierror, RuntimeError ):
88 ''' Extension cache operation failed. '''
90 def __init__( self, cache_path: __.Path, message: str ):
91 self.cache_path = cache_path
92 super( ).__init__( f"Cache error at '{cache_path}': {message}" )
95class ExtensionConfigurationInvalidity( Omnierror, ValueError ):
96 ''' Extension configuration is invalid. '''
98 def __init__( self, extension_name: str, message: str ):
99 self.extension_name = extension_name
100 super( ).__init__( f"Extension '{extension_name}': {message}" )
103class ExtensionInstallFailure( Omnierror, RuntimeError ):
104 ''' Extension package installation failed. '''
106 def __init__( self, package_spec: str, message: str ):
107 self.package_spec = package_spec
108 super( ).__init__( f"Failed to install '{package_spec}': {message}" )
111class ExtensionRegisterFailure( Omnierror, TypeError ):
112 ''' Invalid plugin could not be registered. '''
114 def __init__( self, message: str ):
115 # TODO: Canned message with extension name as argument.
116 super( ).__init__( message )
119class ExtensionVersionConflict( Omnierror, ImportError ):
120 ''' Extension has incompatible version requirements. '''
122 def __init__( self, package_name: str, required: str, available: str ):
123 self.package_name = package_name
124 self.required = required
125 self.available = available
126 super( ).__init__(
127 f"Version conflict for '{package_name}': "
128 f"required {required}, available {available}" )
131class HttpContentTypeInvalidity( Omnierror, ValueError ):
132 ''' HTTP content type is not suitable for requested operation. '''
134 def __init__( self, url: str, content_type: str, operation: str ):
135 self.url = url
136 self.content_type = content_type
137 self.operation = operation
138 super( ).__init__(
139 f"Content type '{content_type}' not suitable for {operation} "
140 f"operation on URL: {url}" )
143class InventoryFilterInvalidity( Omnierror, ValueError ):
144 ''' Inventory filter is invalid. '''
146 def __init__( self, message: str ):
147 super( ).__init__( message )
150class InventoryInaccessibility( Omnierror, RuntimeError ):
151 ''' Inventory file or resource absent or inaccessible. '''
153 def __init__( self, source: str, cause: str | Exception ):
154 message = f"Inventory at '{source}' is inaccessible. Cause: {cause}"
155 self.source = source
156 super( ).__init__( message )
159class InventoryInvalidity( Omnierror, ValueError ):
160 ''' Inventory has invalid format or cannot be parsed. '''
162 def __init__( self, source: str, cause: str | Exception ):
163 message = f"Inventory at '{source}' is invalid. Cause: {cause}"
164 self.source = source
165 super( ).__init__( message )
168class InventoryUrlInvalidity( Omnierror, ValueError ):
169 ''' Inventory URL is malformed or invalid. '''
171 def __init__( self, source: str ):
172 message = f"Invalid URL format: {source}"
173 self.source = source
174 super( ).__init__( message )
177class InventoryUrlNoSupport( Omnierror, NotImplementedError ):
178 ''' Inventory URL has unsupported component. '''
180 def __init__(
181 self, url: _urlparse.ParseResult, component: str,
182 value: __.Absential[ str ] = __.absent,
183 ):
184 url_s = _urlparse.urlunparse( url )
185 message_c = f"Component '{component}' "
186 message_i = f"not supported in inventory URL '{url_s}'."
187 message = (
188 f"{message_c} {message_i}" if __.is_absent( value )
189 else f"{message_c} with value '{value}' {message_i}" )
190 self.url = url
191 super( ).__init__( message )
194class ProcessorGenusInvalidity( Omnierror, ValueError ):
195 ''' Invalid processor genus provided. '''
197 def __init__( self, genus: __.typx.Any ):
198 message = f"Invalid ProcessorGenera: {genus}"
199 self.genus = genus
200 super( ).__init__( message )
203class ProcessorInavailability( Omnierror, RuntimeError ):
204 ''' No processor found to handle source. '''
206 def __init__( self, source: str ):
207 message = f"No processor found to handle source: {source}"
208 self.source = source
209 super( ).__init__( message )
212class ProcessorInvalidity( Omnierror, TypeError ):
213 ''' Processor has wrong type. '''
215 def __init__( self, expected: str, actual: type ):
216 message = f"Expected {expected}, got {actual}."
217 self.expected_type = expected
218 self.actual_type = actual
219 super( ).__init__( message )
222class StructureIncompatibility( Omnierror, ValueError ):
223 ''' Documentation structure incompatible with processor. '''
225 def __init__( self, processor_name: str, source: str ):
226 self.processor_name = processor_name
227 self.source = source
228 super( ).__init__(
229 f"No content extracted by {processor_name} from {source}. "
230 f"The documentation structure may be incompatible with "
231 f"this processor." )
234class StructureProcessFailure( Omnierror, RuntimeError ):
235 ''' Structure processor failed to complete processing. '''
237 def __init__( self, processor_name: str, source: str, cause: str ):
238 self.processor_name = processor_name
239 self.source = source
240 super( ).__init__(
241 f"Processor {processor_name} failed processing {source}. "
242 f"Cause: {cause}" )
245class ContentExtractFailure( StructureProcessFailure ):
246 ''' Failed to extract meaningful content from documentation. '''
248 def __init__(
249 self,
250 processor_name: str,
251 source: str,
252 meaningful_results: int,
253 requested_objects: int,
254 ):
255 self.processor_name = processor_name
256 self.source = source
257 self.meaningful_results = meaningful_results
258 self.requested_objects = requested_objects
259 cause = (
260 f"Got {meaningful_results} meaningful results from "
261 f"{requested_objects} requested objects. "
262 f"This may indicate incompatible theme or documentation "
263 f"structure." )
264 super( ).__init__( processor_name, source, cause )
267class ThemeDetectFailure( StructureProcessFailure ):
268 ''' Theme detection failed during processing. '''
270 def __init__( self, processor_name: str, source: str, theme_error: str ):
271 self.theme_error = theme_error
272 super( ).__init__(
273 processor_name, source, f"Theme detection failed: {theme_error}" )
276class UrlImpermissibility( Omnierror, PermissionError ):
277 ''' URL access blocked by robots.txt directive. '''
279 def __init__( self, url: str, user_agent: str ):
280 message = (
281 f"URL '{url}' blocked by robots.txt for "
282 f"user agent '{user_agent}'" )
283 self.url = url
284 self.user_agent = user_agent
285 super( ).__init__( message )