2121 List ,
2222 Optional ,
2323 Tuple ,
24- TypeGuard ,
2524 TypeVar ,
25+ Generic ,
2626 TypedDict ,
2727 Union ,
2828 BinaryIO ,
2929 TextIO ,
3030 Literal ,
3131 cast ,
32+ TYPE_CHECKING ,
3233)
3334
35+ if sys .version_info >= (3 , 10 ):
36+ from typing import TypeGuard
37+ else :
38+ if TYPE_CHECKING :
39+ from typing_extensions import TypeGuard
40+
3441## DAP type references
3542
3643T = TypeVar ("T" )
44+ Te = TypeVar ("Te" ) # Generic type for event body
45+ Ta = TypeVar ("Ta" ) # Generic type for request arguments
46+ Tb = TypeVar ("Tb" ) # Generic type for response body
3747
3848
39- class Event (TypedDict ):
49+ class Event (Generic [ Te ], TypedDict ):
4050 type : Literal ["event" ]
41- seq : Literal [ 0 ]
51+ seq : int
4252 event : str
43- body : Optional [dict ]
53+ body : Optional [Te ]
4454
4555
46- class Request (TypedDict ):
56+ class Request (Generic [ Ta ], TypedDict , total = False ):
4757 type : Literal ["request" ]
4858 seq : int
4959 command : str
50- arguments : Optional [ dict ]
60+ arguments : Ta
5161
5262
53- class Response (TypedDict ):
63+ class Response (Generic [ Tb ], TypedDict ):
5464 type : Literal ["response" ]
55- seq : Literal [ 0 ]
65+ seq : int
5666 request_seq : int
5767 success : bool
5868 command : str
5969 message : Optional [str ]
60- body : Optional [dict ]
70+ body : Optional [Tb ]
6171
6272
6373ProtocolMessage = Union [Event , Request , Response ]
@@ -94,14 +104,18 @@ class LaunchArguments(AttachOrLaunchArguments, total=False):
94104 launchCommands : List [str ]
95105
96106
97- class AttachArguments (AttachOrLaunchArguments , total = False ):
107+ # Using the function form of TypedDict to allow for hyphenated keys.
108+ AttachGdbServer = TypedDict (
109+ "AttachGdbServer" , {"gdb-remote-port" : int , "gdb-remote-hostname" : str }, total = False
110+ )
111+
112+
113+ class AttachArguments (AttachGdbServer , AttachOrLaunchArguments , total = False ):
98114 program : str
99115 pid : int
100116 waitFor : bool
101117 attachCommands : List [str ]
102118 coreFile : str
103- gdbRemotePort : int
104- gdbRemoteHostname : str
105119
106120
107121class BreakpointData (TypedDict , total = False ):
@@ -159,7 +173,7 @@ def dump_memory(base_addr, data, num_per_line, outfile):
159173
160174
161175def read_packet (
162- f : IO [bytes ], verbose : bool = False , trace_file : Optional [IO [str ]] = None
176+ f : IO [bytes ], trace_file : Optional [IO [str ]] = None
163177) -> Optional [ProtocolMessage ]:
164178 """Decode a JSON packet that starts with the content length and is
165179 followed by the JSON bytes from a file 'f'. Returns None on EOF.
@@ -172,19 +186,11 @@ def read_packet(
172186 prefix = "Content-Length: "
173187 if line .startswith (prefix ):
174188 # Decode length of JSON bytes
175- if verbose :
176- print (f"content: { line } " )
177189 length = int (line [len (prefix ) :])
178- if verbose :
179- print (f"length: { length } " )
180190 # Skip empty line
181191 line = f .readline ().decode ()
182- if verbose :
183- print (f"empty: { line !r} " )
184192 # Read JSON bytes
185193 json_str = f .read (length )
186- if verbose :
187- print (f"json: { json_str !r} " )
188194 if trace_file :
189195 trace_file .write (f"from adapter:\n { json_str !r} \n " )
190196 # Decode the JSON bytes into a python dictionary
@@ -217,12 +223,12 @@ def name(self) -> Optional[str]:
217223 def __init__ (
218224 self , path : Optional [str ] = None , source_reference : Optional [int ] = None
219225 ):
220- self .path = path
221- self .source_reference = source_reference
222-
223226 if path is None and source_reference is None :
224227 raise ValueError ("Either path or source_reference must be provided" )
225228
229+ self .path = path
230+ self .source_reference = source_reference
231+
226232 def to_DAP (self ) -> dict :
227233 if self .path :
228234 return {"path" : self .path , "name" : self .name }
@@ -238,7 +244,7 @@ def __init__(
238244 self ,
239245 recv : BinaryIO ,
240246 send : BinaryIO ,
241- init_commands : list [str ],
247+ init_commands : List [str ],
242248 log_file : Optional [TextIO ] = None ,
243249 ):
244250 # For debugging test failures, try setting `trace_file = sys.stderr`.
@@ -334,10 +340,10 @@ def _recv_packet(
334340 predicate : Optional [Callable [[ProtocolMessage ], bool ]] = None ,
335341 timeout : Optional [float ] = None ,
336342 ) -> Optional [ProtocolMessage ]:
337- """Processes recived packets from the adapter.
343+ """Processes received packets from the adapter.
338344
339345 Updates the DebugCommunication stateful properties based on the received
340- packets in the order they are recieved .
346+ packets in the order they are received .
341347
342348 NOTE: The only time the session state properties should be updated is
343349 during this call to ensure consistency during tests.
@@ -394,7 +400,7 @@ def _handle_event(self, packet: Event) -> None:
394400 event = packet ["event" ]
395401 body : Optional [Dict ] = packet .get ("body" , None )
396402
397- if event == "output" :
403+ if event == "output" and body :
398404 # Store any output we receive so clients can retrieve it later.
399405 category = body ["category" ]
400406 output = body ["output" ]
@@ -408,21 +414,19 @@ def _handle_event(self, packet: Event) -> None:
408414 # When a new process is attached or launched, remember the
409415 # details that are available in the body of the event
410416 self .process_event_body = body
411- elif event == "exited" :
417+ elif event == "exited" and body :
412418 # Process exited, mark the status to indicate the process is not
413419 # alive.
414420 self .exit_status = body ["exitCode" ]
415- elif event == "continued" :
421+ elif event == "continued" and body :
416422 # When the process continues, clear the known threads and
417423 # thread_stop_reasons.
418- all_threads_continued = (
419- body .get ("allThreadsContinued" , True ) if body else True
420- )
424+ all_threads_continued = body .get ("allThreadsContinued" , True )
421425 tid = body ["threadId" ]
422426 if tid in self .thread_stop_reasons :
423427 del self .thread_stop_reasons [tid ]
424428 self ._process_continued (all_threads_continued )
425- elif event == "stopped" :
429+ elif event == "stopped" and body :
426430 # Each thread that stops with a reason will send a
427431 # 'stopped' event. We need to remember the thread stop
428432 # reasons since the 'threads' command doesn't return
@@ -435,10 +439,12 @@ def _handle_event(self, packet: Event) -> None:
435439 # and 'progressEnd' events. Keep these around in case test
436440 # cases want to verify them.
437441 self .progress_events .append (packet )
438- elif event == "breakpoint" :
442+ elif event == "breakpoint" and body :
439443 # Breakpoint events are sent when a breakpoint is resolved
440444 self ._update_verified_breakpoints ([body ["breakpoint" ]])
441- elif event == "capabilities" :
445+ elif event == "capabilities" and body :
446+ if self .capabilities is None :
447+ self .capabilities = {}
442448 # Update the capabilities with new ones from the event.
443449 self .capabilities .update (body ["capabilities" ])
444450
@@ -496,26 +502,23 @@ def _process_continued(self, all_threads_continued: bool):
496502 self .thread_stop_reasons = {}
497503
498504 def _update_verified_breakpoints (self , breakpoints : list [Breakpoint ]):
499- for breakpoint in breakpoints :
505+ for bp in breakpoints :
500506 # If no id is set, we cannot correlate the given breakpoint across
501507 # requests, ignore it.
502- if "id" not in breakpoint :
508+ if "id" not in bp :
503509 continue
504510
505- self .resolved_breakpoints [str (breakpoint ["id" ])] = breakpoint .get (
506- "verified" , False
507- )
511+ self .resolved_breakpoints [str (bp ["id" ])] = bp .get ("verified" , False )
508512
509- def _send_recv (self , request : Request ) -> Optional [Response ]:
513+ def _send_recv (self , request : Request [ Ta ] ) -> Optional [Response [ Tb ] ]:
510514 """Send a command python dictionary as JSON and receive the JSON
511515 response. Validates that the response is the correct sequence and
512516 command in the reply. Any events that are received are added to the
513517 events list in this object"""
514518 seq = self .send_packet (request )
515519 response = self .receive_response (seq )
516520 if response is None :
517- desc = 'no response for "%s"' % (request ["command" ])
518- raise ValueError (desc )
521+ raise ValueError (f"no response for { request !r} " )
519522 self .validate_response (request , response )
520523 return response
521524
@@ -592,7 +595,9 @@ def collect_output(
592595 """
593596 deadline = time .monotonic () + timeout_secs
594597 output = self .get_output (category , clear )
595- while deadline >= time .monotonic () and pattern is None or pattern not in output :
598+ while deadline >= time .monotonic () and (
599+ pattern is None or pattern not in output
600+ ):
596601 event = self .wait_for_event (["output" ], timeout = deadline - time .monotonic ())
597602 if not event : # Timeout or EOF
598603 break
@@ -874,7 +879,11 @@ def request_attach(
874879 args_dict ["gdb-remote-port" ] = gdbRemotePort
875880 if gdbRemoteHostname is not None :
876881 args_dict ["gdb-remote-hostname" ] = gdbRemoteHostname
877- command_dict = {"command" : "attach" , "type" : "request" , "arguments" : args_dict }
882+ command_dict : Request = {
883+ "command" : "attach" ,
884+ "type" : "request" ,
885+ "arguments" : args_dict ,
886+ }
878887 return self ._send_recv (command_dict )
879888
880889 def request_breakpointLocations (
@@ -1130,7 +1139,11 @@ def request_launch(
11301139 args_dict ["displayExtendedBacktrace" ] = displayExtendedBacktrace
11311140 if commandEscapePrefix is not None :
11321141 args_dict ["commandEscapePrefix" ] = commandEscapePrefix
1133- command_dict = {"command" : "launch" , "type" : "request" , "arguments" : args_dict }
1142+ command_dict : Request = {
1143+ "command" : "launch" ,
1144+ "type" : "request" ,
1145+ "arguments" : args_dict ,
1146+ }
11341147 return self ._send_recv (command_dict )
11351148
11361149 def request_next (self , threadId , granularity = "statement" ):
@@ -1190,12 +1203,14 @@ def request_setBreakpoints(
11901203 self ,
11911204 source : Union [Source , str ],
11921205 line_array : Optional [List [int ]],
1193- data : Optional [List [BreakpiontData ]] = None ,
1206+ data : Optional [List [BreakpointData ]] = None ,
11941207 ):
11951208 """data is array of parameters for breakpoints in line_array.
11961209 Each parameter object is 1:1 mapping with entries in line_entry.
11971210 It contains optional location/hitCondition/logMessage parameters.
11981211 """
1212+ if isinstance (source , str ):
1213+ source = Source (path = source )
11991214 args_dict = {
12001215 "source" : source .to_DAP (),
12011216 "sourceModified" : False ,
@@ -1204,20 +1219,20 @@ def request_setBreakpoints(
12041219 args_dict ["lines" ] = line_array
12051220 breakpoints = []
12061221 for i , line in enumerate (line_array ):
1207- breakpoint_data : BreakpiontData = {}
1222+ breakpoint_data : BreakpointData = {}
12081223 if data is not None and i < len (data ):
12091224 breakpoint_data = data [i ]
12101225 bp : SourceBreakpoint = {"line" : line , ** breakpoint_data }
12111226 breakpoints .append (bp )
12121227 args_dict ["breakpoints" ] = breakpoints
12131228
1214- command_dict = {
1229+ command_dict : Request = {
12151230 "command" : "setBreakpoints" ,
12161231 "type" : "request" ,
12171232 "arguments" : args_dict ,
12181233 }
12191234 response = self ._send_recv (command_dict )
1220- if response ["success" ]:
1235+ if response and response ["success" ] and response [ "body " ]:
12211236 self ._update_verified_breakpoints (response ["body" ]["breakpoints" ])
12221237 return response
12231238
0 commit comments