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

47 statements  

« prev     ^ index     » next       coverage.py v7.3.4, created at 2023-12-20 14:17 -0500

1import sys 

2import traceback 

3import textwrap 

4 

5from zapy.utils.singleton import SingletonMeta 

6 

7from typing_extensions import TypedDict 

8 

9 

10class TracebackInfo(TypedDict): 

11 exception_type: str 

12 exception_message: str 

13 line: int 

14 stacktrace: str 

15 

16 

17def 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 

23def recover_traceback(exc_obj: BaseException): 

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

25 

26def 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 

34class 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