Skip to content

Instantly share code, notes, and snippets.

@exhuma
Last active March 5, 2024 11:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save exhuma/ed63842c66f68193c7a8bb7cee5b2d67 to your computer and use it in GitHub Desktop.
Save exhuma/ed63842c66f68193c7a8bb7cee5b2d67 to your computer and use it in GitHub Desktop.
Fancy Colorful Python Traceback
'''
This module provides a function ``make_except_hook`` which returns a function
usable as replacement for ``sys.excepthook``.
All imports are done dynamically to make this function a self-contained,
copy/pasteable piece of code.
Requires the "blessings" module to work!
--- License - (MIT) ----------------------------------------------------------
Copyright 2018 Michel ALBERT
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
'''
def make_except_hook(ignore_patterns, obfuscate=False):
'''
Create a function which can be used as a replacement for
``sys.excepthook``.
This new function does several things:
* Filenames are shown as relative names to the current working folder.
This can make some traceback messages a lot more readable.
* Output is colorised
* Colorisation can be disabled for frames in certain filenames (for
example, you can disable colorisation for everything in the ``env``
folder). This helps highlighting your own code.
* In addition to ignore colorisation it is also (optionally) possible
to completely hide messages originating from files in the ignore
list.
*ignore_patterns* contains a list of regex patterns. If any of those
patterns matches the (relative) filename, the message will not be
colorised.
If *obfuscate* is true, all entries matching any of the *ignore_patterns*
will be completely hidden. This can make your own errors stand out more,
but might hide important errors if not careful!
This may not be the best thing to use in production either! But then
again... having an exception bubble up in production code is not really
intended.
This requires the package ``blessings`` to be installed or it will break!
Example Usage:
>>> import sys
>>> sys.excepthook = make_except_hook([r'^env/.*'], obfuscate=True)
'''
import re
from os.path import relpath
from textwrap import wrap
from blessings import Terminal
patterns = [re.compile(pat) for pat in ignore_patterns]
def colorhook(type, value, traceback):
current = traceback
term = Terminal()
print(term.red('┌─── Traceback ───'))
while current:
frame = current.tb_frame
code = frame.f_code
# The function (or other code-object) which caused the exception in
# this frame
origin_in_code = code.co_name
relfile = relpath(code.co_filename)
matching_pattern_mask = [p.match(relfile) for p in patterns]
# Fetch the line which caused the problem
with open(code.co_filename) as fptr:
code_lines = fptr.readlines()
error_line = code_lines[frame.f_lineno-1].strip()
if any(matching_pattern_mask):
if obfuscate:
print('{t.red}│{t.normal} ***'.format(t=term))
else:
template = (
'{t.red}│{t.normal} File "%s", '
'line %s, '
'in %s\n'
'{t.red}│{t.normal} %s'
).format(t=term)
print(template % (relfile, frame.f_lineno, origin_in_code,
error_line))
else:
template = (
'{t.bold}{t.red}│{t.white} File "{t.blue}%s{t.white}", '
'line {t.yellow}%s{t.white}, '
'in {t.cyan}%s{t.normal}\n'
'{t.red}│{t.normal} {t.green}%s{t.normal}'
).format(t=term)
print(template % (relfile, frame.f_lineno, origin_in_code,
error_line))
current = current.tb_next
error_message = '%s: %s' % (type.__name__, value)
print(term.red('├─── Message ─────────'))
message_lines = wrap(error_message,
initial_indent=term.red('│ '),
subsequent_indent=term.red('│ '))
print('\n'.join(message_lines))
print(term.red('└─────────────────'))
return colorhook
import sys
from colortrace import make_except_hook
sys.excepthook = make_except_hook(['^env.*'], obfuscate=True)
def bar():
return '%s' % (1, 2)
def foo():
bar()
foo()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment