Skip to content

Commit 3c9ccb7

Browse files
authored
fix: don't break the monkeypatch chain (#91)
Mainly libs change this hook, a common practice is to run the existing hook after or before the custom code. This avoids breaking monkeypatching of other libs.
1 parent 680eb24 commit 3c9ccb7

File tree

3 files changed

+37
-5
lines changed

3 files changed

+37
-5
lines changed

daiquiri/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,17 @@ def setup(
109109
if set_excepthook:
110110
program_logger = logging.getLogger(program_name)
111111

112+
initial_excepthook = staticmethod(sys.excepthook)
113+
112114
def logging_excepthook(
113-
exc_type: typing.Optional[typing.Type[BaseException]],
114-
value: typing.Optional[BaseException],
115+
exc_type: typing.Type[BaseException],
116+
value: BaseException,
115117
tb: typing.Optional[_ptypes.TracebackType],
116118
) -> None:
117119
program_logger.critical(
118120
"".join(traceback.format_exception(exc_type, value, tb))
119121
)
122+
initial_excepthook(exc_type, value, tb)
120123

121124
sys.excepthook = logging_excepthook
122125

daiquiri/tests/test_daiquiri.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,53 @@
1212
import io
1313
import json
1414
import logging
15+
import sys
16+
import types
1517
import typing
1618
import unittest
1719
import warnings
1820

1921
import daiquiri
2022

23+
real_excepthook = sys.excepthook
24+
2125

2226
class TestDaiquiri(unittest.TestCase):
2327
def tearDown(self) -> None:
2428
# Be sure to reset the warning capture
2529
logging.captureWarnings(False)
30+
# Reset exception hook
31+
sys.excepthook = real_excepthook
2632
super(TestDaiquiri, self).tearDown()
2733

2834
def test_setup(self) -> None:
2935
daiquiri.setup()
3036
daiquiri.setup(level=logging.DEBUG)
3137
daiquiri.setup(program_name="foobar")
3238

39+
def test_excepthook(self) -> None:
40+
hook_ran = False
41+
42+
def catcher(
43+
exctype: type[BaseException],
44+
value: BaseException,
45+
traceback: types.TracebackType | None,
46+
) -> None:
47+
nonlocal hook_ran
48+
hook_ran = True
49+
real_excepthook(exctype, value, traceback)
50+
51+
sys.excepthook = catcher
52+
53+
sys.excepthook(Exception, Exception("boom"), None)
54+
assert hook_ran
55+
56+
daiquiri.setup()
57+
58+
hook_ran = False
59+
sys.excepthook(Exception, Exception("boom"), None)
60+
assert hook_ran
61+
3362
def test_setup_json_formatter(self) -> None:
3463
stream = io.StringIO()
3564
daiquiri.setup(
@@ -67,7 +96,7 @@ def test_capture_warnings(self) -> None:
6796
line = stream.getvalue()
6897
self.assertIn("WARNING py.warnings: ", line)
6998
self.assertIn(
70-
"daiquiri/tests/test_daiquiri.py:66: "
99+
"daiquiri/tests/test_daiquiri.py:95: "
71100
'UserWarning: omg!\n warnings.warn("omg!")\n',
72101
line,
73102
)

daiquiri/tests/test_formatter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def test_empty_keywords(self) -> None:
7171
)
7272

7373
def test_keywords_no_extras(self) -> None:
74-
format_string = "%(levelname)s %(name)s" " %(test)s%(extras)s: %(message)s"
74+
format_string = "%(levelname)s %(name)s %(test)s%(extras)s: %(message)s"
7575
formatter = daiquiri.formatter.ColorExtrasFormatter(
7676
fmt=format_string, keywords={"test"}
7777
)
@@ -81,7 +81,7 @@ def test_keywords_no_extras(self) -> None:
8181
self.assertEqual(self.stream.getvalue(), "INFO my_module a: test message\n")
8282

8383
def test_keywords_with_extras(self) -> None:
84-
format_string = "%(levelname)s %(name)s" " %(test)s%(extras)s: %(message)s"
84+
format_string = "%(levelname)s %(name)s %(test)s%(extras)s: %(message)s"
8585
formatter = daiquiri.formatter.ColorExtrasFormatter(
8686
fmt=format_string, keywords={"test"}
8787
)

0 commit comments

Comments
 (0)