|
39 | 39 | 'line': '', |
40 | 40 | 'internal-failure': '', |
41 | 41 | 'internal-detail': '', |
| 42 | + 'source-failure': '', |
| 43 | + 'source-detail': '', |
42 | 44 | } |
43 | 45 | EVENT_COLORS = { |
44 | 46 | 'reset': Style.RESET_ALL, |
|
54 | 56 | 'vars-name': Style.BRIGHT, |
55 | 57 | 'internal-failure': Back.RED + Style.BRIGHT + Fore.RED, |
56 | 58 | 'internal-detail': Fore.WHITE, |
| 59 | + 'source-failure': Style.BRIGHT + Back.YELLOW + Fore.YELLOW, |
| 60 | + 'source-detail': Fore.WHITE, |
57 | 61 | } |
58 | 62 | CODE_COLORS = { |
59 | 63 | 'call': Fore.RESET + Style.BRIGHT, |
@@ -202,21 +206,26 @@ def source(self, getlines=linecache.getlines): |
202 | 206 | Get a line from ``linecache``. Ignores failures somewhat. |
203 | 207 | """ |
204 | 208 | 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 |
218 | 210 | 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] |
220 | 229 |
|
221 | 230 | __getitem__ = object.__getattribute__ |
222 | 231 |
|
@@ -469,14 +478,23 @@ def __init__(self, stream=sys.stderr, filename_alignment=DEFAULT_MIN_FILENAME_AL |
469 | 478 | self.stream = stream |
470 | 479 | self.filename_alignment = filename_alignment |
471 | 480 |
|
| 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 | + |
472 | 490 | def __call__(self, event, sep=os.path.sep, join=os.path.join): |
473 | 491 | """ |
474 | 492 | Handle event and print filename, line number and source code. If event.kind is a `return` or `exception` also prints values. |
475 | 493 | """ |
476 | 494 | filename = event.filename or "<???>" |
477 | 495 | # TODO: support auto-alignment, need a context object for this, eg: |
478 | 496 | # 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) |
480 | 498 | self.stream.write("{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {code}{}{reset}\n".format( |
481 | 499 | join(*filename.split(sep)[-2:]), |
482 | 500 | event.lineno, |
|
0 commit comments