2525import traceback
2626import sys
2727import queue
28- from typing import TYPE_CHECKING , NamedTuple , Optional
28+ from typing import TYPE_CHECKING , NamedTuple , Optional , TypedDict
29+ from types import TracebackType
2930
3031from .version import ELECTRUM_VERSION
3132from . import constants
@@ -68,9 +69,16 @@ class BaseCrashReporter(Logger):
6869 USER_COMMENT_PLACEHOLDER = _ ("Do not enter sensitive/private information here. "
6970 "The report will be visible on the public issue tracker." )
7071
71- def __init__ (self , exctype , value , tb ):
72+ exc_args : tuple [type [BaseException ], BaseException , TracebackType | None ]
73+
74+ def __init__ (
75+ self ,
76+ exctype : type [BaseException ],
77+ excvalue : BaseException ,
78+ tb : TracebackType | None ,
79+ ):
7280 Logger .__init__ (self )
73- self .exc_args = (exctype , value , tb )
81+ self .exc_args = (exctype , excvalue , tb )
7482
7583 def send_report (self , asyncio_loop , proxy : 'ProxySettings' , * , timeout = None ) -> CrashReportResponse :
7684 # FIXME the caller needs to catch generic "Exception", as this method does not have a well-defined API...
@@ -82,7 +90,7 @@ def send_report(self, asyncio_loop, proxy: 'ProxySettings', *, timeout=None) ->
8290 ] and ".electrum.org" in BaseCrashReporter .report_server ):
8391 # Gah! Some kind of altcoin wants to send us crash reports.
8492 raise Exception (_ ("Missing report URL." ))
85- report = self .get_traceback_info ()
93+ report = self .get_traceback_info (* self . exc_args )
8694 report .update (self .get_additional_info ())
8795 report = json .dumps (report )
8896 coro = self .do_post (proxy , BaseCrashReporter .report_server + "/crash.json" , data = report )
@@ -111,19 +119,25 @@ async def do_post(self, proxy: 'ProxySettings', url, data) -> str:
111119 async with session .post (url , data = data , raise_for_status = True ) as resp :
112120 return await resp .text ()
113121
114- def get_traceback_info (self ):
115- exc_string = str (self .exc_args [1 ])
116- stack = traceback .extract_tb (self .exc_args [2 ])
117- readable_trace = self .__get_traceback_str_to_send ()
118- id = {
122+ @classmethod
123+ def get_traceback_info (
124+ cls ,
125+ exctype : type [BaseException ],
126+ excvalue : BaseException ,
127+ tb : TracebackType | None ,
128+ ) -> TypedDict ('TBInfo' , {'exc_string' : str , 'stack' : str , 'id' : dict [str , str ]}):
129+ exc_string = str (excvalue )
130+ stack = traceback .extract_tb (tb )
131+ readable_trace = cls ._get_traceback_str_to_send (exctype , excvalue , tb )
132+ _id = {
119133 "file" : stack [- 1 ].filename if len (stack ) else '<no stack>' ,
120134 "name" : stack [- 1 ].name if len (stack ) else '<no stack>' ,
121- "type" : self . exc_args [ 0 ] .__name__
122- }
135+ "type" : exctype .__name__
136+ } # note: this is the "id" the crash reporter server uses to group together reports.
123137 return {
124138 "exc_string" : exc_string ,
125139 "stack" : readable_trace ,
126- "id" : id
140+ "id" : _id ,
127141 }
128142
129143 def get_additional_info (self ):
@@ -142,15 +156,21 @@ def get_additional_info(self):
142156 pass
143157 return args
144158
145- def __get_traceback_str_to_send (self ) -> str :
159+ @classmethod
160+ def _get_traceback_str_to_send (
161+ cls ,
162+ exctype : type [BaseException ],
163+ excvalue : BaseException ,
164+ tb : TracebackType | None ,
165+ ) -> str :
146166 # make sure that traceback sent to crash reporter contains
147167 # e.__context__ and e.__cause__, i.e. if there was a chain of
148168 # exceptions, we want the full traceback for the whole chain.
149- return "" .join (traceback .format_exception (* self . exc_args ))
169+ return "" .join (traceback .format_exception (exctype , excvalue , tb ))
150170
151171 def _get_traceback_str_to_display (self ) -> str :
152172 # overridden in Qt subclass
153- return self .__get_traceback_str_to_send ( )
173+ return self ._get_traceback_str_to_send ( * self . exc_args )
154174
155175 def get_report_string (self ):
156176 info = self .get_additional_info ()
0 commit comments