Skip to content

Commit 1c6eb5d

Browse files
committed
Implement a prettier fallback for when no sources are available for that frame.
1 parent 17a3965 commit 1c6eb5d

1 file changed

Lines changed: 33 additions & 15 deletions

File tree

src/hunter.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
'line': '',
4040
'internal-failure': '',
4141
'internal-detail': '',
42+
'source-failure': '',
43+
'source-detail': '',
4244
}
4345
EVENT_COLORS = {
4446
'reset': Style.RESET_ALL,
@@ -54,6 +56,8 @@
5456
'vars-name': Style.BRIGHT,
5557
'internal-failure': Back.RED + Style.BRIGHT + Fore.RED,
5658
'internal-detail': Fore.WHITE,
59+
'source-failure': Style.BRIGHT + Back.YELLOW + Fore.YELLOW,
60+
'source-detail': Fore.WHITE,
5761
}
5862
CODE_COLORS = {
5963
'call': Fore.RESET + Style.BRIGHT,
@@ -202,21 +206,26 @@ def source(self, getlines=linecache.getlines):
202206
Get a line from ``linecache``. Ignores failures somewhat.
203207
"""
204208
try:
205-
if self.kind == 'call' and self.code.co_name != "<module>":
206-
lines = []
207-
try:
208-
for _, token, _, _, line in tokenize.generate_tokens(partial(
209-
next,
210-
yield_lines(self.filename, self.lineno - 1, lines.append)
211-
)):
212-
if token in ("def", "class", "lambda"):
213-
return ''.join(lines)
214-
except tokenize.TokenError:
215-
pass
216-
217-
return getlines(self.filename)[self.lineno - 1]
209+
return self._raw_source
218210
except Exception as exc:
219-
return "??? no source: {!r} ???".format(exc)
211+
return "??? NO SOURCE: {!r}".format(exc)
212+
213+
214+
@CachedProperty
215+
def _raw_source(self, getlines=linecache.getlines):
216+
if self.kind == 'call' and self.code.co_name != "<module>":
217+
lines = []
218+
try:
219+
for _, token, _, _, line in tokenize.generate_tokens(partial(
220+
next,
221+
yield_lines(self.filename, self.lineno - 1, lines.append)
222+
)):
223+
if token in ("def", "class", "lambda"):
224+
return ''.join(lines)
225+
except tokenize.TokenError:
226+
pass
227+
228+
return getlines(self.filename)[self.lineno - 1]
220229

221230
__getitem__ = object.__getattribute__
222231

@@ -469,14 +478,23 @@ def __init__(self, stream=sys.stderr, filename_alignment=DEFAULT_MIN_FILENAME_AL
469478
self.stream = stream
470479
self.filename_alignment = filename_alignment
471480

481+
def _safe_source(self, event):
482+
try:
483+
lines = event._raw_source.rstrip().splitlines()
484+
if not lines:
485+
raise RuntimeError("Source code string is empty.")
486+
return lines
487+
except Exception as exc:
488+
return "{source-failure}??? NO SOURCE: {source-detail}{!r}".format(exc, **self.event_colors),
489+
472490
def __call__(self, event, sep=os.path.sep, join=os.path.join):
473491
"""
474492
Handle event and print filename, line number and source code. If event.kind is a `return` or `exception` also prints values.
475493
"""
476494
filename = event.filename or "<???>"
477495
# TODO: support auto-alignment, need a context object for this, eg:
478496
# alignment = context.filename_alignment = max(getattr(context, 'filename_alignment', self.filename_alignment), len(filename))
479-
lines = event.source.rstrip().splitlines()
497+
lines = self._safe_source(event)
480498
self.stream.write("{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {code}{}{reset}\n".format(
481499
join(*filename.split(sep)[-2:]),
482500
event.lineno,

0 commit comments

Comments
 (0)