|
1
|
|
|
# -*- coding: utf-8 |
|
2
|
|
|
"""APIs to add suggestions to exceptions.""" |
|
3
|
|
|
from didyoumean_internal import add_suggestions_to_exception |
|
4
|
|
|
import functools |
|
5
|
|
|
import sys |
|
6
|
|
|
|
|
7
|
|
|
|
|
8
|
|
|
def didyoumean_decorator(func): |
|
9
|
|
|
"""Decorator to add suggestions to exceptions. |
|
10
|
|
|
|
|
11
|
|
|
To use it, decorate one of the functions called, for instance 'main()': |
|
12
|
|
|
@didyoumean_decorator |
|
13
|
|
|
def main(): |
|
14
|
|
|
some_code |
|
15
|
|
|
""" |
|
16
|
|
|
@functools.wraps(func) |
|
17
|
|
|
def decorated(*args, **kwargs): |
|
18
|
|
|
"""Function returned by the decorator.""" |
|
19
|
|
|
try: |
|
20
|
|
|
return func(*args, **kwargs) |
|
21
|
|
|
except: |
|
22
|
|
|
type_, value, traceback = sys.exc_info() |
|
23
|
|
|
add_suggestions_to_exception(type_, value, traceback) |
|
24
|
|
|
raise |
|
25
|
|
|
return decorated |
|
26
|
|
|
|
|
27
|
|
|
|
|
28
|
|
|
def didyoumean_postmortem(): |
|
29
|
|
|
"""Post postem function to add suggestions to last exception thrown. |
|
30
|
|
|
|
|
31
|
|
|
Add suggestions to last exception thrown (in interactive mode) and |
|
32
|
|
|
return it (which should print it). |
|
33
|
|
|
""" |
|
34
|
|
|
if hasattr(sys, 'last_type'): |
|
35
|
|
|
typ, val, trace = sys.last_type, sys.last_value, sys.last_traceback |
|
36
|
|
|
add_suggestions_to_exception(typ, val, trace) |
|
37
|
|
|
return val |
|
38
|
|
|
return None |
|
39
|
|
|
|
|
40
|
|
|
|
|
41
|
|
|
class didyoumean_contextmanager(object): |
|
42
|
|
|
"""Context manager to add suggestions to exceptions. |
|
43
|
|
|
|
|
44
|
|
|
To use it, create a context: |
|
45
|
|
|
with didyoumean_contextmanager(): |
|
46
|
|
|
some_code. |
|
47
|
|
|
""" |
|
48
|
|
|
|
|
49
|
|
|
def __enter__(self): |
|
50
|
|
|
"""Method called when entering the context manager. |
|
51
|
|
|
|
|
52
|
|
|
Not relevant here (does not do anything). |
|
53
|
|
|
""" |
|
54
|
|
|
pass |
|
55
|
|
|
|
|
56
|
|
|
def __exit__(self, type_, value, traceback): |
|
57
|
|
|
"""Method called when exiting the context manager. |
|
58
|
|
|
|
|
59
|
|
|
Add suggestions to the exception (if any). |
|
60
|
|
|
""" |
|
61
|
|
|
assert (type_ is None) == (value is None) |
|
62
|
|
|
if value is not None: |
|
63
|
|
|
if isinstance(value, type_): |
|
64
|
|
|
# Error is not re-raised as it is the caller's responsability |
|
65
|
|
|
# but the error is enhanced nonetheless |
|
66
|
|
|
add_suggestions_to_exception(type_, value, traceback) |
|
67
|
|
|
else: |
|
68
|
|
|
# Python 2.6 bug : http://bugs.python.org/issue7853 |
|
69
|
|
|
# Instead of having the exception, we have its representation |
|
70
|
|
|
# We can try to rebuild the exception, add suggestions to it |
|
71
|
|
|
# and re-raise it (re-raise shouldn't be done normally but it |
|
72
|
|
|
# is a dirty work-around for a dirty issue). |
|
73
|
|
|
if isinstance(value, str): |
|
74
|
|
|
value = type_(value) |
|
75
|
|
|
else: |
|
76
|
|
|
value = type_(*value) |
|
77
|
|
|
add_suggestions_to_exception(type_, value, traceback) |
|
78
|
|
|
raise value |
|
79
|
|
|
|
|
80
|
|
|
|
|
81
|
|
|
def didyoumean_hook(type_, value, traceback, prev_hook=sys.excepthook): |
|
82
|
|
|
"""Hook to be substituted to sys.excepthook to enhance exceptions.""" |
|
83
|
|
|
add_suggestions_to_exception(type_, value, traceback) |
|
84
|
|
|
return prev_hook(type_, value, traceback) |
|
85
|
|
|
|
|
86
|
|
|
|
|
87
|
|
|
def didyoumean_custom_exc(shell, etype, evalue, tb, tb_offset=None): |
|
88
|
|
|
"""Custom exception handler to replace the iPython one.""" |
|
89
|
|
|
add_suggestions_to_exception(etype, evalue, tb) |
|
90
|
|
|
return shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset) |
|
91
|
|
|
|
|
92
|
|
|
|
|
93
|
|
|
def set_ipython_custom_exc(func): |
|
94
|
|
|
"""Try to set the custom exception handler for iPython.""" |
|
95
|
|
|
# https://mail.scipy.org/pipermail/ipython-dev/2012-April/008945.html |
|
96
|
|
|
# http://stackoverflow.com/questions/1261668/cannot-override-sys-excepthook |
|
97
|
|
|
try: |
|
98
|
|
|
get_ipython().set_custom_exc((Exception,), func) |
|
99
|
|
|
except NameError: |
|
100
|
|
|
pass # get_ipython does not exist - ignore |
|
101
|
|
|
|
|
102
|
|
|
|
|
103
|
|
|
def didyoumean_enablehook(): |
|
104
|
|
|
"""Function to set hooks to their custom value.""" |
|
105
|
|
|
sys.excepthook = didyoumean_hook |
|
106
|
|
|
set_ipython_custom_exc(didyoumean_custom_exc) |
|
107
|
|
|
|
|
108
|
|
|
|
|
109
|
|
|
def didyoumean_disablehook(): |
|
110
|
|
|
"""Function to set hooks to their normal value.""" |
|
111
|
|
|
sys.excepthook = sys.__excepthook__ |
|
112
|
|
|
set_ipython_custom_exc(None) |
|
113
|
|
|
|
|
114
|
|
|
# NOTE: It could be funny to have a magic command in Python |
|
115
|
|
|
# https://ipython.org/ipython-doc/dev/config/custommagics.html |
|
116
|
|
|
|