PTWZ's picture
Upload folder using huggingface_hub
f5f3483 verified
# Copyright 2024 The etils Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utils to reraise."""
from __future__ import annotations
import contextlib
from typing import Callable, Iterator, NoReturn, Optional, Union
# TODO(epot):
# * Better stacktrace:
# * Do not set `__suppress_context__` if the original exception has a
# context (careful about nested exceptions).
# * Do not have the reraise appear in the stacktrace ?
# * Design `epy.ReraiseMsg()` for better representation when nesting errors
# (error in `x/y/z/`, rather than `error in x/: error in y/: error in z/:`)
# * Should automatically detect the current exception ?
# * Should unify `maybe_reraise` & `reraise` ?
# prefix/suffix can be:
# * A string
# * A lazy string (only computed if exception is reraised)
_Str = Union[str, Callable[[], str]]
def reraise(
e: Exception,
prefix: Optional[_Str] = None,
suffix: Optional[_Str] = None,
) -> NoReturn:
"""Reraise an exception with an additional message.
Benefit: Contrary to `raise ... from ...` and
`raise Exception().with_traceback(tb)`, this function will:
* Keep the original exception type, attributes,...
* Avoid multi-nested `During handling of the above exception, another
exception occurred`. Only the single original stacktrace is displayed.
This result in cleaner and more compact error messages.
Usage:
```
try:
fn(x)
except Exception as e:
epy.reraise(e, prefix=f'Error for {x}: ')
```
Args:
e: Exception to reraise
prefix: Prefix to add to the exception message.
suffix: Suffix to add to the exception message.
"""
# Lazy-evaluate functions
prefix = prefix() if callable(prefix) else prefix
suffix = suffix() if callable(suffix) else suffix
prefix = prefix or ''
suffix = '\n' + suffix if suffix else ''
msg = f'{prefix}{e}{suffix}'
# Dynamically create an exception for:
# * Compatibility with caller core (e.g. `except OriginalError`)
class WrappedException(type(e)):
"""Exception proxy with additional message."""
def __init__(self, msg):
# We explicitly bypass super() as the `type(e).__init__` constructor
# might have special kwargs
Exception.__init__(self, msg) # pylint: disable=non-parent-init-called
def __getattr__(self, name: str):
# Capture `e` through closure. We do not pass e through __init__
# to bypass `Exception.__new__` magic which add `__str__` artifacts.
return getattr(e, name)
# The wrapped exception might have overwritten `__str__` & cie, so
# use the base exception ones.
__repr__ = BaseException.__repr__
__str__ = BaseException.__str__
WrappedException.__name__ = type(e).__name__
WrappedException.__qualname__ = type(e).__qualname__
WrappedException.__module__ = type(e).__module__
new_exception = WrappedException(msg)
# Propagate the exception:
# * `with_traceback` will propagate the original stacktrace
# * `from e.__cause__` will:
# * Propagate the original `__cause__` (likely `None`)
# * Set `__suppress_context__` to True, so `__context__` isn't displayed
# This avoid multiple `During handling of the above exception, another
# exception occurred:` messages when nesting `reraise`
raise new_exception.with_traceback(e.__traceback__) from e.__cause__
@contextlib.contextmanager
def maybe_reraise(
prefix: Optional[_Str] = None,
suffix: Optional[_Str] = None,
) -> Iterator[None]:
"""Context manager which reraise exceptions with an additional message.
Benefit: Contrary to `raise ... from ...` and
`raise Exception().with_traceback(tb)`, this function will:
* Keep the original exception type, attributes,...
* Avoid multi-nested `During handling of the above exception, another
exception occurred`. Only the single original stacktrace is displayed.
This result in cleaner and more compact error messages.
Usage:
```python
with epy.maybe_reraise(prefix=f'Error for {x}:'):
fn(x)
```
Args:
prefix: Prefix to add to the exception message. Can be a function for
lazy-evaluation.
suffix: Suffix to add to the exception message. Can be a function for
lazy-evaluation.
Yields:
None
"""
try:
yield
except Exception as e: # pylint: disable=broad-except
reraise(e, prefix=prefix, suffix=suffix)