Coverage for src\zapy\templating\traceback.py: 60%

1 import sys

2 import traceback

3 import textwrap

4

5 from zapy.utils.singleton import SingletonMeta

6

7 from typing_extensions import TypedDict

8

9

10 class TracebackInfo(TypedDict):

11 exception_type: str

12 exception_message: str

13 line: int

14 stacktrace: str

15

16

17 def annotate_traceback(exc_obj: BaseException, script="", location=""):

18 traceback_info = TracebackHandler().extract(script, location)

19 exc_obj._parse_errors = traceback_info

20 exc_obj.add_note(traceback_info['stacktrace'])

21 return traceback_info

22

23 def recover_traceback(exc_obj: BaseException):

24 return getattr(exc_obj, "_parse_errors", None)

25

26 def copy_traceback(exc_obj: BaseException, from_exc: BaseException):

27 info = recover_traceback(from_exc)

28 if info:

29 exc_obj._parse_errors = info

30 exc_obj.add_note(info["stacktrace"])

31 return info

32

33

34 class TracebackHandler(metaclass=SingletonMeta):

35 header = "Traceback (most recent call last):"

36

37 def extract(self, script="", location="") -> TracebackInfo:

38 exc_type, exc_obj, exc_tb = sys.exc_info()

39 frame_summary = traceback.extract_tb(exc_tb)[-1]

40 if frame_summary.filename == "<string>": 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true

41 return self.extract_from_frame(frame_summary, exc_obj, script=script, location=location)

42 else:

43 return self.extract_from_error(exc_obj, location=location)

44

45 def extract_from_frame(self, frame_summary: traceback.FrameSummary, exc_obj, script='', location=''):

46 stacktrace = textwrap.dedent(f"""\

47 {self.header}

48 {location}, line {frame_summary.lineno}, in {frame_summary.name}

49 {frame_summary.line or self.__extract_line(script, frame_summary.lineno)}

50 {exc_obj.__class__.__name__}: {exc_obj}""")

51 return {

52 "exception_type": exc_obj.__class__.__name__,

53 "exception_message": str(exc_obj),

54 "line": frame_summary.lineno,

55 "stacktrace": stacktrace,

56 }

57

58 def extract_from_error(self, exc_obj, location=''):

59 tracelines = traceback.format_exception_only(exc_obj)

60 tracelines[0] = tracelines[0].replace('File "<string>"', location)

61 tracelines.insert(0, self.header + '\n')

62 return {

63 "exception_type": exc_obj.__class__.__name__,

64 "exception_message": str(exc_obj),

65 "line": exc_obj.lineno if isinstance(exc_obj, SyntaxError) else None,

66 "stacktrace": "".join(tracelines),

67 }

68

69 def __extract_line(self, script: str, line: int):

70 if not script:

71 return ''

72 try:

73 script_lines = script.split('\n')

74 return script_lines[line - 1].strip()

75 except Exception:

76 return "<<Zapy: line omitted due to an error on extraction, check your line separation>>"

77