Show More
@@ -0,0 +1,5 b'' | |||
|
1 | Support for PEP-678 Exception Notes | |
|
2 | ----------------------------------- | |
|
3 | ||
|
4 | Ultratb now shows :pep:`678` notes, improving your debugging experience on | |
|
5 | Python 3.11+ or with libraries such as Pytest and Hypothesis. |
@@ -27,6 +27,7 b' __pycache__' | |||
|
27 | 27 | *.swp |
|
28 | 28 | .pytest_cache |
|
29 | 29 | .python-version |
|
30 | .venv*/ | |
|
30 | 31 | venv*/ |
|
31 | 32 | .mypy_cache/ |
|
32 | 33 |
@@ -363,6 +363,29 b' def r3o2():' | |||
|
363 | 363 | ip.run_cell("r3o2()") |
|
364 | 364 | |
|
365 | 365 | |
|
366 | class PEP678NotesReportingTest(unittest.TestCase): | |
|
367 | ERROR_WITH_NOTE = """ | |
|
368 | try: | |
|
369 | raise AssertionError("Message") | |
|
370 | except Exception as e: | |
|
371 | try: | |
|
372 | e.add_note("This is a PEP-678 note.") | |
|
373 | except AttributeError: # Python <= 3.10 | |
|
374 | e.__notes__ = ("This is a PEP-678 note.",) | |
|
375 | raise | |
|
376 | """ | |
|
377 | ||
|
378 | def test_verbose_reports_notes(self): | |
|
379 | with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): | |
|
380 | ip.run_cell(self.ERROR_WITH_NOTE) | |
|
381 | ||
|
382 | def test_plain_reports_notes(self): | |
|
383 | with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): | |
|
384 | ip.run_cell("%xmode Plain") | |
|
385 | ip.run_cell(self.ERROR_WITH_NOTE) | |
|
386 | ip.run_cell("%xmode Verbose") | |
|
387 | ||
|
388 | ||
|
366 | 389 | #---------------------------------------------------------------------------- |
|
367 | 390 | |
|
368 | 391 | # module testing (minimal) |
@@ -89,6 +89,7 b' Inheritance diagram:' | |||
|
89 | 89 | #***************************************************************************** |
|
90 | 90 | |
|
91 | 91 | |
|
92 | from collections.abc import Sequence | |
|
92 | 93 | import functools |
|
93 | 94 | import inspect |
|
94 | 95 | import linecache |
@@ -183,6 +184,14 b' def get_line_number_of_frame(frame: types.FrameType) -> int:' | |||
|
183 | 184 | return count_lines_in_py_file(filename) |
|
184 | 185 | |
|
185 | 186 | |
|
187 | def _safe_string(value, what, func=str): | |
|
188 | # Copied from cpython/Lib/traceback.py | |
|
189 | try: | |
|
190 | return func(value) | |
|
191 | except: | |
|
192 | return f"<{what} {func.__name__}() failed>" | |
|
193 | ||
|
194 | ||
|
186 | 195 | def _format_traceback_lines(lines, Colors, has_colors: bool, lvals): |
|
187 | 196 | """ |
|
188 | 197 | Format tracebacks lines with pointing arrow, leading numbers... |
@@ -582,7 +591,7 b' class ListTB(TBTools):' | |||
|
582 | 591 | """ |
|
583 | 592 | |
|
584 | 593 | Colors = self.Colors |
|
585 | list = [] | |
|
594 | output_list = [] | |
|
586 | 595 | for ind, (filename, lineno, name, line) in enumerate(extracted_list): |
|
587 | 596 | normalCol, nameCol, fileCol, lineCol = ( |
|
588 | 597 | # Emphasize the last entry |
@@ -600,9 +609,9 b' class ListTB(TBTools):' | |||
|
600 | 609 | item += "\n" |
|
601 | 610 | if line: |
|
602 | 611 | item += f"{lineCol} {line.strip()}{normalCol}\n" |
|
603 | list.append(item) | |
|
612 | output_list.append(item) | |
|
604 | 613 | |
|
605 | return list | |
|
614 | return output_list | |
|
606 | 615 | |
|
607 | 616 | def _format_exception_only(self, etype, value): |
|
608 | 617 | """Format the exception part of a traceback. |
@@ -619,11 +628,11 b' class ListTB(TBTools):' | |||
|
619 | 628 | """ |
|
620 | 629 | have_filedata = False |
|
621 | 630 | Colors = self.Colors |
|
622 | list = [] | |
|
631 | output_list = [] | |
|
623 | 632 | stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal) |
|
624 | 633 | if value is None: |
|
625 | 634 | # Not sure if this can still happen in Python 2.6 and above |
|
626 |
list.append(stype + |
|
|
635 | output_list.append(stype + "\n") | |
|
627 | 636 | else: |
|
628 | 637 | if issubclass(etype, SyntaxError): |
|
629 | 638 | have_filedata = True |
@@ -634,7 +643,7 b' class ListTB(TBTools):' | |||
|
634 | 643 | else: |
|
635 | 644 | lineno = "unknown" |
|
636 | 645 | textline = "" |
|
637 | list.append( | |
|
646 | output_list.append( | |
|
638 | 647 | "%s %s%s\n" |
|
639 | 648 | % ( |
|
640 | 649 | Colors.normalEm, |
@@ -654,28 +663,33 b' class ListTB(TBTools):' | |||
|
654 | 663 | i = 0 |
|
655 | 664 | while i < len(textline) and textline[i].isspace(): |
|
656 | 665 | i += 1 |
|
657 |
list.append( |
|
|
658 |
|
|
|
659 | Colors.Normal)) | |
|
666 | output_list.append( | |
|
667 | "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal) | |
|
668 | ) | |
|
660 | 669 | if value.offset is not None: |
|
661 | 670 | s = ' ' |
|
662 | 671 | for c in textline[i:value.offset - 1]: |
|
663 | 672 | if c.isspace(): |
|
664 | 673 | s += c |
|
665 | 674 | else: |
|
666 |
s += |
|
|
667 |
list.append( |
|
|
668 |
|
|
|
675 | s += " " | |
|
676 | output_list.append( | |
|
677 | "%s%s^%s\n" % (Colors.caret, s, Colors.Normal) | |
|
678 | ) | |
|
669 | 679 | |
|
670 | 680 | try: |
|
671 | 681 | s = value.msg |
|
672 | 682 | except Exception: |
|
673 | 683 | s = self._some_str(value) |
|
674 | 684 | if s: |
|
675 | list.append('%s%s:%s %s\n' % (stype, Colors.excName, | |
|
676 | Colors.Normal, s)) | |
|
685 | output_list.append( | |
|
686 | "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s) | |
|
687 | ) | |
|
677 | 688 | else: |
|
678 |
list.append( |
|
|
689 | output_list.append("%s\n" % stype) | |
|
690 | ||
|
691 | # PEP-678 notes | |
|
692 | output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", [])) | |
|
679 | 693 | |
|
680 | 694 | # sync with user hooks |
|
681 | 695 | if have_filedata: |
@@ -683,7 +697,7 b' class ListTB(TBTools):' | |||
|
683 | 697 | if ipinst is not None: |
|
684 | 698 | ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) |
|
685 | 699 | |
|
686 | return list | |
|
700 | return output_list | |
|
687 | 701 | |
|
688 | 702 | def get_exception_only(self, etype, value): |
|
689 | 703 | """Only print the exception type and message, without a traceback. |
@@ -999,9 +1013,27 b' class VerboseTB(TBTools):' | |||
|
999 | 1013 | # User exception is improperly defined. |
|
1000 | 1014 | etype, evalue = str, sys.exc_info()[:2] |
|
1001 | 1015 | etype_str, evalue_str = map(str, (etype, evalue)) |
|
1016 | ||
|
1017 | # PEP-678 notes | |
|
1018 | notes = getattr(evalue, "__notes__", []) | |
|
1019 | if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)): | |
|
1020 | notes = [_safe_string(notes, "__notes__", func=repr)] | |
|
1021 | ||
|
1002 | 1022 | # ... and format it |
|
1003 | return ['%s%s%s: %s' % (colors.excName, etype_str, | |
|
1004 | colorsnormal, py3compat.cast_unicode(evalue_str))] | |
|
1023 | return [ | |
|
1024 | "{}{}{}: {}".format( | |
|
1025 | colors.excName, | |
|
1026 | etype_str, | |
|
1027 | colorsnormal, | |
|
1028 | py3compat.cast_unicode(evalue_str), | |
|
1029 | ), | |
|
1030 | *( | |
|
1031 | "{}{}".format( | |
|
1032 | colorsnormal, _safe_string(py3compat.cast_unicode(n), "note") | |
|
1033 | ) | |
|
1034 | for n in notes | |
|
1035 | ), | |
|
1036 | ] | |
|
1005 | 1037 | |
|
1006 | 1038 | def format_exception_as_a_whole( |
|
1007 | 1039 | self, |
@@ -1068,7 +1100,7 b' class VerboseTB(TBTools):' | |||
|
1068 | 1100 | if ipinst is not None: |
|
1069 | 1101 | ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) |
|
1070 | 1102 | |
|
1071 |
return [[head] + frames + |
|
|
1103 | return [[head] + frames + formatted_exception] | |
|
1072 | 1104 | |
|
1073 | 1105 | def get_records( |
|
1074 | 1106 | self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int |
General Comments 0
You need to be logged in to leave comments.
Login now