lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xerr/__init__.py (about) 1 # -*- coding: utf-8 -*- 2 # Copyright (C) 2018 Nexedi SA and Contributors. 3 # Kirill Smelkov <kirr@nexedi.com> 4 # 5 # This program is free software: you can Use, Study, Modify and Redistribute 6 # it under the terms of the GNU General Public License version 3, or (at your 7 # option) any later version, as published by the Free Software Foundation. 8 # 9 # You can also Link and Combine this program with other software covered by 10 # the terms of any of the Free Software licenses or any of the Open Source 11 # Initiative approved licenses and Convey the resulting work. Corresponding 12 # source of such a combination shall include the source code for all other 13 # software used. 14 # 15 # This program is distributed WITHOUT ANY WARRANTY; without even the implied 16 # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 # 18 # See COPYING file for full licensing terms. 19 # See https://www.nexedi.com/licensing for rationale and options. 20 """Package xerr provides addons for error-handling. 21 22 Context is handy to concisely add context to raised error, for example: 23 24 def myfunc(arg1, arg2): 25 with xerr.context("doing something %s" % name): 26 do1() 27 do2() 28 ... 29 30 will wrap an exception that inner code block raises (if any) with 31 32 Error("doing something %s" % name, ...) 33 34 The original unwrapped error will be still accessible as the cause of returned 35 error with 36 37 xerr.cause(err) 38 39 This package is modelled after equally-named Go package. 40 """ 41 42 import traceback 43 44 # Error is exception that represents an error. 45 # 46 # It has error string/object (.err), and optionally cause exception that 47 # caused this error. 48 # 49 # It is similar to `raise ... from ...` in python3. 50 class Error(Exception): 51 def __init__(self, err, cause_exc=None, cause_tb=None): 52 self.err, self.cause_exc, self.cause_tb = err, cause_exc, cause_tb 53 54 # __str__ returns string representation of the error. 55 # 56 # besides main error line, it also includes cause traceback, if cause is 57 # not "well-defined-error". 58 def __str__(self): 59 errv = [] # of .err + .cause_exc in the end 60 e = self 61 while isinstance(e, Error): 62 errv.append(e.err) 63 tb = e.cause_tb 64 e = e.cause_exc 65 66 # !Error cause 67 if e is not None: 68 errv.append(e) 69 70 s = ": ".join(["%s" % _ for _ in errv]) 71 72 # cause traceback 73 if tb is not None: 74 if not well_defined(e): 75 s += "\n\ncause traceback:\n%s" % ('\n'.join(traceback.format_tb(tb)),) 76 77 return s 78 79 def __repr__(self): 80 return "Error(%r, %r, %r)" % (self.err, self.cause_exc, self.cause_tb) 81 82 83 # well_defined returns whether err is well-defined error or not. 84 # 85 # well-defined-errors are those that carry on all information and do not need 86 # traceback to interpret them if they are wrapped with another Error properly. 87 _wde_classv = () 88 _wde_objectv = () 89 def well_defined(err): 90 if isinstance(err, _wde_classv): 91 return True 92 93 for e in _wde_objectv: 94 if e is err: 95 return True 96 97 return False 98 99 # register_wde_* let Error know that an error type or object is well-defined. 100 def register_wde_class(exc_class): 101 global _wde_classv 102 _wde_classv += (exc_class,) 103 104 def register_wde_object(exc_object): 105 global _wde_objectv 106 _wde_objectv += (exc_object,) 107 108 register_wde_class(Error) 109 110 111 # cause returns deepest !None cause of an error. 112 def cause(err): 113 while isinstance(err, Error): 114 if err.cause_exc is None: 115 return err 116 err = err.cause_exc 117 118 return err 119 120 # context is context manager to wrap code block with an error context. 121 # 122 # for example 123 # 124 # with xerr.context("doing something %s" % name): 125 # do1() 126 # do2() 127 # ... 128 # 129 # will wrap an exception that inner code block raises with Error. 130 class context(object): 131 132 def __init__(self, errprefix): 133 self.errprefix = errprefix 134 135 def __enter__(self): 136 return self 137 138 def __exit__(self, exc_type, exc_val, exc_tb): 139 if exc_val is not None: 140 raise Error(self.errprefix, exc_val, exc_tb)