litl / backoff

Python library providing function decorators for configurable backoff and retry
MIT License
2.61k stars 148 forks source link

Improve Typing and Error Handling in `on_exception` #194

Open davidslater opened 1 year ago

davidslater commented 1 year ago

Currently, on_exception takes an argument exception: _MaybeSequence[Type[Exception]].

However, this cannot handle any Sequence of Type[Exception]. For instance, if a list[Type[Exception]] is given, as in the following code:

import backoff

i: int = 0
@backoff.on_exception(backoff.expo, [ValueError])
def _retry():
    global i
    i += 1
    print(f"i = {i}")
    if i < 3:
        raise ValueError(f"failure {i}")
    if i == 3:
        raise RuntimeError("runtime error")

    return "success"

_retry()

then a hard-to-understand stack trace is generated:

i = 1
Traceback (most recent call last):
  File "/Users/davidslater/git/litl/backoff/backoff/_sync.py", line 107, in retry
    ret = target(*args, **kwargs)
  File "/Users/davidslater/git/litl/backoff/script.py", line 10, in _retry
    raise ValueError(f"failure {i}")
ValueError: failure 1

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/davidslater/git/litl/backoff/script.py", line 16, in <module>
    _retry()
  File "/Users/davidslater/git/litl/backoff/backoff/_sync.py", line 108, in retry
    except exception as e:
TypeError: catching classes that do not inherit from BaseException is not allowed

Similar issues can also arise with the same traceback, such as:

@backoff.on_exception(backoff.expo, ValueError())

which are hard to understand and debug, as there are no lines between the function call in user code and the except exception as e in the library.

I see two major different ways to improve this: A) modify the type information. If done directly, this would change the type to Union[Type[Exception], Tuple[Type[Exception]]], Alternatively, you could add to _typing.py:

_MaybeTuple = Union[T, Tuple[T]]

and then change the type to _MaybeTuple[Type[Exception]].

B) Do checking of exception when it is given to the function to improve error response. Essentially, just check if it is an Exception or a tuple of exception types and raise a ValueError if not.