Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Or you can patch explicitly in your code and execute the script/module as usual
import coredumpy
# Create a dump in "./dumps" when there's an unhandled exception
coredumpy.patch_except(directory='./dumps')
# Create a dump in "./dumps" only when exceptions other than KeyboardInterrupt occur
coredumpy.patch_except(directory="./dumps", exclude=[KeyboardInterrupt])
# Create a dump in "./dumps" when there's a unittest failure/error
coredumpy.patch_unittest(directory='./dumps')
# Create a dump in "./dumps" when there's a pytest failure/error
Expand Down
19 changes: 16 additions & 3 deletions src/coredumpy/except_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@

import sys
import traceback
from typing import Callable, Optional, Union
from typing import Callable, Optional, Union, Iterable, Type

from .coredumpy import dump


Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last thing, why did you remove this blank line? Let's try to avoid the cosmetic changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice. It might be when adding the from typing import type, the formatter automatically intervenes. Reverting.

_original_excepthook = sys.excepthook


def patch_except(path: Optional[Union[str, Callable[[], str]]] = None,
directory: Optional[str] = None):
directory: Optional[str] = None,
exclude: Optional[Iterable[Type[BaseException]]] = None):
""" Patch the excepthook to dump the frame stack when an unhandled exception occurs.

@param path:
The path to save the dump file. It could be a string or a callable that returns a string.
if not specified, the default filename will be used
@param directory:
The directory to save the dump file, only works when path is not specified.
@param exclude:
A list of exception types to exclude from dumping. If an exception is in this list,
it will not be dumped and the original excepthook will be called instead.
"""
if exclude is not None:
_exclude = tuple(exclude)
if any(not issubclass(excCls, BaseException) for excCls in _exclude):
raise TypeError('Expect `exclude` to be BaseException subclasses.')
else:
_exclude = tuple()

def _get_description(type, value, tb):
side_count = (70 - len(type.__qualname__) - 2) // 2
Expand All @@ -30,6 +39,10 @@ def _get_description(type, value, tb):
''.join(traceback.format_exception(type, value, tb)).strip()])

def _excepthook(type, value, tb):
if isinstance(value, _exclude):
_original_excepthook(type, value, tb)
return

while tb.tb_next:
tb = tb.tb_next

Expand Down
18 changes: 18 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,24 @@ def g(arg):
self.assertIn("return 1 / arg", stdout)
self.assertIn("0", stdout)

def test_except_exclude(self):
script = """
import coredumpy
coredumpy.patch_except(path='coredumpy_dump', exclude=[ValueError])
raise ValueError
"""
stdout, stderr = self.run_script(script, expected_returncode=1)
self.assertIn("ValueError", stderr)
self.assertNotIn("Your frame stack is dumped", stdout)

def test_except_exclude_sanity(self):
script = """
import coredumpy
coredumpy.patch_except(path='coredumpy_dump', exclude=[int])
"""
_, stderr = self.run_script(script, expected_returncode=1)
self.assertIn("TypeError:", stderr)

def test_cli(self):
script = """
def g(arg):
Expand Down