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)