exception_wrap 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: exception_wrap
3
+ Version: 0.1.0
4
+ Summary: An exception wrapper for decorating functions with detailed generic error handling and logging.
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Exception Wrapper
9
+
10
+ Rather than needing to remember all of the exception types or do "catch all" exceptions, and rather than having to reimplement logging formats constantly,
11
+ this module provides consistent and comprehensive logging and error handing approach.
12
+
13
+
@@ -0,0 +1,5 @@
1
+ lib.py,sha256=E1o_wzl3hx-OJJTbzlCN6cr-nXRRiwPLpG0jPU25zCA,17627
2
+ exception_wrap-0.1.0.dist-info/METADATA,sha256=2oluAPEepSpOYklKrRFXCyygt0rn9dHrKw8xEZgOitE,492
3
+ exception_wrap-0.1.0.dist-info/WHEEL,sha256=YLJXdYXQ2FQ0Uqn2J-6iEIC-3iOey8lH3xCtvFLkd8Q,91
4
+ exception_wrap-0.1.0.dist-info/top_level.txt,sha256=oyXcrLgLICoBS0ILk_wZBhkAAY-M4hbQoMsA1hDsf5c,4
5
+ exception_wrap-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (81.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ lib
lib.py ADDED
@@ -0,0 +1,498 @@
1
+ """
2
+ Generic Exception Handler and Logger Module
3
+
4
+ Provides a decorator for automatic exception handling and logging with detailed
5
+ information for all standard Python built-in exceptions.
6
+ """
7
+
8
+ import functools
9
+ import sys
10
+ from datetime import datetime, timezone
11
+ from typing import Callable, Any, Optional
12
+ import uuid
13
+
14
+
15
+ def log_exception(
16
+ exception_id: str,
17
+ func_name: str,
18
+ exc_type: type,
19
+ exc_value: Exception,
20
+ exc_info: tuple,
21
+ logged_args: dict = None
22
+ ) -> None:
23
+ """
24
+ Log exception details in a structured format.
25
+
26
+ Args:
27
+ exception_id: UUIDv4 string for tracking this exception
28
+ func_name: Name of the function where exception occurred
29
+ exc_type: Type of the exception
30
+ exc_value: The exception instance
31
+ exc_info: Exception info tuple from sys.exc_info()
32
+ logged_args: Dictionary of log_this_* arguments to include in log
33
+ """
34
+ timestamp = datetime.now(timezone.utc).isoformat()
35
+
36
+ # Extract helpful information
37
+ exc_msg = str(exc_value) if str(exc_value) else "No message provided"
38
+
39
+ # Get line number if available
40
+ tb = exc_info[2]
41
+ line_no = tb.tb_lineno if tb else "Unknown"
42
+
43
+ # Format logged arguments if present
44
+ logged_args_str = ""
45
+ if logged_args:
46
+ # Sort for consistent ordering in logs
47
+ sorted_args = sorted(logged_args.items())
48
+ args_parts = [f"{k}: {v}" for k, v in sorted_args]
49
+ logged_args_str = f"logged args: {', '.join(args_parts)} - "
50
+
51
+ # Format the log message
52
+ log_msg = (
53
+ f"{timestamp} - {exception_id} - {func_name} - "
54
+ f"{logged_args_str}"
55
+ f"ERROR: {exc_type.__name__}: {exc_msg} (Line: {line_no})"
56
+ )
57
+
58
+ print(log_msg, file=sys.stderr)
59
+
60
+
61
+ def exception_handler(func: Callable) -> Callable:
62
+ """
63
+ Decorator that wraps a function with comprehensive exception handling.
64
+
65
+ The decorated function should accept 'exception_id' and 'func_name' as
66
+ keyword arguments, or they will be auto-generated/detected.
67
+
68
+ Usage:
69
+ @exception_handler
70
+ def my_function(arg1, arg2, exception_id=None, func_name=None):
71
+ # function code here
72
+ pass
73
+ """
74
+ @functools.wraps(func)
75
+ def wrapper(*args, **kwargs):
76
+ # Extract or generate tracking information
77
+ exception_id = kwargs.pop('exception_id', str(uuid.uuid4()))
78
+ func_name = kwargs.pop('func_name', func.__name__)
79
+
80
+ # Extract all log_this_* arguments
81
+ logged_args = {}
82
+ keys_to_remove = []
83
+ for key in kwargs:
84
+ if key.startswith('log_this_'):
85
+ # Remove the 'log_this_' prefix for cleaner logging
86
+ clean_key = key[9:] # len('log_this_') = 9
87
+ logged_args[clean_key] = kwargs[key]
88
+ keys_to_remove.append(key)
89
+
90
+ # Remove log_this_* arguments from kwargs
91
+ for key in keys_to_remove:
92
+ kwargs.pop(key)
93
+
94
+ try:
95
+ return func(*args, **kwargs)
96
+
97
+ # Concrete Exceptions - Most Specific First
98
+
99
+ # System Exit Exceptions
100
+ except KeyboardInterrupt as e:
101
+ exc_info = sys.exc_info()
102
+ log_exception(exception_id, func_name, KeyboardInterrupt, e, exc_info, logged_args)
103
+ raise
104
+
105
+ except SystemExit as e:
106
+ exc_info = sys.exc_info()
107
+ log_exception(exception_id, func_name, SystemExit, e, exc_info, logged_args)
108
+ raise
109
+
110
+ # OS and I/O Exceptions
111
+ except FileNotFoundError as e:
112
+ exc_info = sys.exc_info()
113
+ log_exception(exception_id, func_name, FileNotFoundError, e, exc_info, logged_args)
114
+ raise
115
+
116
+ except FileExistsError as e:
117
+ exc_info = sys.exc_info()
118
+ log_exception(exception_id, func_name, FileExistsError, e, exc_info, logged_args)
119
+ raise
120
+
121
+ except IsADirectoryError as e:
122
+ exc_info = sys.exc_info()
123
+ log_exception(exception_id, func_name, IsADirectoryError, e, exc_info, logged_args)
124
+ raise
125
+
126
+ except NotADirectoryError as e:
127
+ exc_info = sys.exc_info()
128
+ log_exception(exception_id, func_name, NotADirectoryError, e, exc_info, logged_args)
129
+ raise
130
+
131
+ except PermissionError as e:
132
+ exc_info = sys.exc_info()
133
+ log_exception(exception_id, func_name, PermissionError, e, exc_info, logged_args)
134
+ raise
135
+
136
+ except ProcessLookupError as e:
137
+ exc_info = sys.exc_info()
138
+ log_exception(exception_id, func_name, ProcessLookupError, e, exc_info, logged_args)
139
+ raise
140
+
141
+ except TimeoutError as e:
142
+ exc_info = sys.exc_info()
143
+ log_exception(exception_id, func_name, TimeoutError, e, exc_info, logged_args)
144
+ raise
145
+
146
+ except InterruptedError as e:
147
+ exc_info = sys.exc_info()
148
+ log_exception(exception_id, func_name, InterruptedError, e, exc_info, logged_args)
149
+ raise
150
+
151
+ except ChildProcessError as e:
152
+ exc_info = sys.exc_info()
153
+ log_exception(exception_id, func_name, ChildProcessError, e, exc_info, logged_args)
154
+ raise
155
+
156
+ except BlockingIOError as e:
157
+ exc_info = sys.exc_info()
158
+ log_exception(exception_id, func_name, BlockingIOError, e, exc_info, logged_args)
159
+ raise
160
+
161
+ except ConnectionError as e:
162
+ exc_info = sys.exc_info()
163
+ log_exception(exception_id, func_name, ConnectionError, e, exc_info, logged_args)
164
+ raise
165
+
166
+ except BrokenPipeError as e:
167
+ exc_info = sys.exc_info()
168
+ log_exception(exception_id, func_name, BrokenPipeError, e, exc_info, logged_args)
169
+ raise
170
+
171
+ except ConnectionAbortedError as e:
172
+ exc_info = sys.exc_info()
173
+ log_exception(exception_id, func_name, ConnectionAbortedError, e, exc_info, logged_args)
174
+ raise
175
+
176
+ except ConnectionRefusedError as e:
177
+ exc_info = sys.exc_info()
178
+ log_exception(exception_id, func_name, ConnectionRefusedError, e, exc_info, logged_args)
179
+ raise
180
+
181
+ except ConnectionResetError as e:
182
+ exc_info = sys.exc_info()
183
+ log_exception(exception_id, func_name, ConnectionResetError, e, exc_info, logged_args)
184
+ raise
185
+
186
+ except OSError as e:
187
+ exc_info = sys.exc_info()
188
+ log_exception(exception_id, func_name, OSError, e, exc_info, logged_args)
189
+ raise
190
+
191
+ # Arithmetic Exceptions
192
+ except ZeroDivisionError as e:
193
+ exc_info = sys.exc_info()
194
+ log_exception(exception_id, func_name, ZeroDivisionError, e, exc_info, logged_args)
195
+ raise
196
+
197
+ except FloatingPointError as e:
198
+ exc_info = sys.exc_info()
199
+ log_exception(exception_id, func_name, FloatingPointError, e, exc_info, logged_args)
200
+ raise
201
+
202
+ except OverflowError as e:
203
+ exc_info = sys.exc_info()
204
+ log_exception(exception_id, func_name, OverflowError, e, exc_info, logged_args)
205
+ raise
206
+
207
+ # Type and Value Exceptions
208
+ except TypeError as e:
209
+ exc_info = sys.exc_info()
210
+ log_exception(exception_id, func_name, TypeError, e, exc_info, logged_args)
211
+ raise
212
+
213
+ except ValueError as e:
214
+ exc_info = sys.exc_info()
215
+ log_exception(exception_id, func_name, ValueError, e, exc_info, logged_args)
216
+ raise
217
+
218
+ except UnicodeError as e:
219
+ exc_info = sys.exc_info()
220
+ log_exception(exception_id, func_name, UnicodeError, e, exc_info, logged_args)
221
+ raise
222
+
223
+ except UnicodeDecodeError as e:
224
+ exc_info = sys.exc_info()
225
+ log_exception(exception_id, func_name, UnicodeDecodeError, e, exc_info, logged_args)
226
+ raise
227
+
228
+ except UnicodeEncodeError as e:
229
+ exc_info = sys.exc_info()
230
+ log_exception(exception_id, func_name, UnicodeEncodeError, e, exc_info, logged_args)
231
+ raise
232
+
233
+ except UnicodeTranslateError as e:
234
+ exc_info = sys.exc_info()
235
+ log_exception(exception_id, func_name, UnicodeTranslateError, e, exc_info, logged_args)
236
+ raise
237
+
238
+ # Lookup Exceptions
239
+ except KeyError as e:
240
+ exc_info = sys.exc_info()
241
+ log_exception(exception_id, func_name, KeyError, e, exc_info, logged_args)
242
+ raise
243
+
244
+ except IndexError as e:
245
+ exc_info = sys.exc_info()
246
+ log_exception(exception_id, func_name, IndexError, e, exc_info, logged_args)
247
+ raise
248
+
249
+ except AttributeError as e:
250
+ exc_info = sys.exc_info()
251
+ log_exception(exception_id, func_name, AttributeError, e, exc_info, logged_args)
252
+ raise
253
+
254
+ except NameError as e:
255
+ exc_info = sys.exc_info()
256
+ log_exception(exception_id, func_name, NameError, e, exc_info, logged_args)
257
+ raise
258
+
259
+ except UnboundLocalError as e:
260
+ exc_info = sys.exc_info()
261
+ log_exception(exception_id, func_name, UnboundLocalError, e, exc_info, logged_args)
262
+ raise
263
+
264
+ # Import Exceptions
265
+ except ModuleNotFoundError as e:
266
+ exc_info = sys.exc_info()
267
+ log_exception(exception_id, func_name, ModuleNotFoundError, e, exc_info, logged_args)
268
+ raise
269
+
270
+ except ImportError as e:
271
+ exc_info = sys.exc_info()
272
+ log_exception(exception_id, func_name, ImportError, e, exc_info, logged_args)
273
+ raise
274
+
275
+ # Memory and Resource Exceptions
276
+ except MemoryError as e:
277
+ exc_info = sys.exc_info()
278
+ log_exception(exception_id, func_name, MemoryError, e, exc_info, logged_args)
279
+ raise
280
+
281
+ except RecursionError as e:
282
+ exc_info = sys.exc_info()
283
+ log_exception(exception_id, func_name, RecursionError, e, exc_info, logged_args)
284
+ raise
285
+
286
+ # Runtime Exceptions
287
+ except NotImplementedError as e:
288
+ exc_info = sys.exc_info()
289
+ log_exception(exception_id, func_name, NotImplementedError, e, exc_info, logged_args)
290
+ raise
291
+
292
+ except StopIteration as e:
293
+ exc_info = sys.exc_info()
294
+ log_exception(exception_id, func_name, StopIteration, e, exc_info, logged_args)
295
+ raise
296
+
297
+ except StopAsyncIteration as e:
298
+ exc_info = sys.exc_info()
299
+ log_exception(exception_id, func_name, StopAsyncIteration, e, exc_info, logged_args)
300
+ raise
301
+
302
+ except GeneratorExit as e:
303
+ exc_info = sys.exc_info()
304
+ log_exception(exception_id, func_name, GeneratorExit, e, exc_info, logged_args)
305
+ raise
306
+
307
+ # Syntax and Indentation Exceptions
308
+ except SyntaxError as e:
309
+ exc_info = sys.exc_info()
310
+ log_exception(exception_id, func_name, SyntaxError, e, exc_info, logged_args)
311
+ raise
312
+
313
+ except IndentationError as e:
314
+ exc_info = sys.exc_info()
315
+ log_exception(exception_id, func_name, IndentationError, e, exc_info, logged_args)
316
+ raise
317
+
318
+ except TabError as e:
319
+ exc_info = sys.exc_info()
320
+ log_exception(exception_id, func_name, TabError, e, exc_info, logged_args)
321
+ raise
322
+
323
+ # System Exceptions
324
+ except SystemError as e:
325
+ exc_info = sys.exc_info()
326
+ log_exception(exception_id, func_name, SystemError, e, exc_info, logged_args)
327
+ raise
328
+
329
+ except ReferenceError as e:
330
+ exc_info = sys.exc_info()
331
+ log_exception(exception_id, func_name, ReferenceError, e, exc_info, logged_args)
332
+ raise
333
+
334
+ # Buffer and EOFError
335
+ except BufferError as e:
336
+ exc_info = sys.exc_info()
337
+ log_exception(exception_id, func_name, BufferError, e, exc_info, logged_args)
338
+ raise
339
+
340
+ except EOFError as e:
341
+ exc_info = sys.exc_info()
342
+ log_exception(exception_id, func_name, EOFError, e, exc_info, logged_args)
343
+ raise
344
+
345
+ # Assertion Error
346
+ except AssertionError as e:
347
+ exc_info = sys.exc_info()
348
+ log_exception(exception_id, func_name, AssertionError, e, exc_info, logged_args)
349
+ raise
350
+
351
+ # Runtime Error and Warning
352
+ except RuntimeError as e:
353
+ exc_info = sys.exc_info()
354
+ log_exception(exception_id, func_name, RuntimeError, e, exc_info, logged_args)
355
+ raise
356
+
357
+ except RuntimeWarning as e:
358
+ exc_info = sys.exc_info()
359
+ log_exception(exception_id, func_name, RuntimeWarning, e, exc_info, logged_args)
360
+ raise
361
+
362
+ # Lookup Error (parent class - catch after specific lookup errors)
363
+ except LookupError as e:
364
+ exc_info = sys.exc_info()
365
+ log_exception(exception_id, func_name, LookupError, e, exc_info, logged_args)
366
+ raise
367
+
368
+ # Arithmetic Error (parent class - catch after specific arithmetic errors)
369
+ except ArithmeticError as e:
370
+ exc_info = sys.exc_info()
371
+ log_exception(exception_id, func_name, ArithmeticError, e, exc_info, logged_args)
372
+ raise
373
+
374
+ # Base Exception classes (catch last)
375
+ except Exception as e:
376
+ exc_info = sys.exc_info()
377
+ log_exception(exception_id, func_name, Exception, e, exc_info, logged_args)
378
+ raise
379
+
380
+ except BaseException as e:
381
+ exc_info = sys.exc_info()
382
+ log_exception(exception_id, func_name, BaseException, e, exc_info, logged_args)
383
+ raise
384
+
385
+ return wrapper
386
+
387
+
388
+ # Convenience function for manual exception handling
389
+ def handle_exception(
390
+ exception_id: Optional[str] = None,
391
+ func_name: Optional[str] = None,
392
+ **logged_args
393
+ ) -> None:
394
+ """
395
+ Manually log the current exception.
396
+
397
+ Call this within an except block to log the exception with the same
398
+ format as the decorator.
399
+
400
+ Args:
401
+ exception_id: Optional UUID for tracking. Generated if not provided.
402
+ func_name: Optional function name. Detected from caller if not provided.
403
+ **logged_args: Any additional context to log (e.g., user_id=123, rate=0.5)
404
+
405
+ Example:
406
+ try:
407
+ risky_operation()
408
+ except Exception:
409
+ handle_exception(
410
+ exception_id="custom-uuid",
411
+ func_name="my_function",
412
+ user_id=12345,
413
+ request_id="abc-123"
414
+ )
415
+ raise
416
+ """
417
+ import inspect
418
+
419
+ exc_info = sys.exc_info()
420
+ if exc_info[0] is None:
421
+ print("Warning: handle_exception called outside of exception context",
422
+ file=sys.stderr)
423
+ return
424
+
425
+ if exception_id is None:
426
+ exception_id = str(uuid.uuid4())
427
+
428
+ if func_name is None:
429
+ frame = inspect.currentframe()
430
+ if frame and frame.f_back:
431
+ func_name = frame.f_back.f_code.co_name
432
+ else:
433
+ func_name = "unknown"
434
+
435
+ log_exception(exception_id, func_name, exc_info[0], exc_info[1], exc_info, logged_args)
436
+
437
+
438
+ if __name__ == "__main__":
439
+ # Example usage and testing
440
+
441
+ @exception_handler
442
+ def test_division_by_zero():
443
+ """Test ZeroDivisionError handling"""
444
+ return 1 / 0
445
+
446
+ @exception_handler
447
+ def test_file_not_found():
448
+ """Test FileNotFoundError handling"""
449
+ with open("/nonexistent/file.txt", "r") as f:
450
+ return f.read()
451
+
452
+ @exception_handler
453
+ def test_key_error():
454
+ """Test KeyError handling"""
455
+ d = {"a": 1}
456
+ return d["b"]
457
+
458
+ @exception_handler
459
+ def test_with_custom_id(data, exception_id=None, func_name=None, log_this_user=None, log_this_rate=None):
460
+ """Test with custom exception_id, func_name, and log_this_* arguments"""
461
+ return data[10] # Will raise IndexError
462
+
463
+ print("Testing exception_logger module...\n")
464
+
465
+ # Test 1: ZeroDivisionError
466
+ print("Test 1: Division by zero")
467
+ try:
468
+ test_division_by_zero()
469
+ except ZeroDivisionError:
470
+ print("Caught and re-raised as expected\n")
471
+
472
+ # Test 2: FileNotFoundError
473
+ print("Test 2: File not found")
474
+ try:
475
+ test_file_not_found()
476
+ except FileNotFoundError:
477
+ print("Caught and re-raised as expected\n")
478
+
479
+ # Test 3: KeyError
480
+ print("Test 3: Key error")
481
+ try:
482
+ test_key_error()
483
+ except KeyError:
484
+ print("Caught and re-raised as expected\n")
485
+
486
+ # Test 4: Custom exception_id and func_name
487
+ print("Test 4: Custom tracking info")
488
+ custom_uuid = str(uuid.uuid4())
489
+ try:
490
+ test_with_custom_id([1, 2, 3],
491
+ exception_id=custom_uuid,
492
+ func_name="custom_function_name",
493
+ log_this_user="frank",
494
+ log_this_rate=0.125)
495
+ except IndexError:
496
+ print("Caught and re-raised as expected\n")
497
+
498
+ print("All tests completed!")