@@ -2539,7 +2539,48 @@ def protocol_version():
25392539 revision = 0
25402540 return int (f"{ v .major :02X} { v .minor :02X} { revision :02X} F0" , 16 )
25412541
2542- def _send (self , ** kwargs ) -> None :
2542+ def _ensure_valid_message (self , msg ):
2543+ # Ensure the message conforms to our protocol.
2544+ # If anything needs to be changed here for a patch release of Python,
2545+ # the 'revision' in protocol_version() should be updated.
2546+ match msg :
2547+ case {"message" : str (), "type" : str ()}:
2548+ # Have the client show a message. The client chooses how to
2549+ # format the message based on its type. The currently defined
2550+ # types are "info" and "error". If a message has a type the
2551+ # client doesn't recognize, it must be treated as "info".
2552+ pass
2553+ case {"help" : str ()}:
2554+ # Have the client show the help for a given argument.
2555+ pass
2556+ case {"prompt" : str (), "state" : str ()}:
2557+ # Have the client display the given prompt and wait for a reply
2558+ # from the user. If the client recognizes the state it may
2559+ # enable mode-specific features like multi-line editing.
2560+ # If it doesn't recognize the state it must prompt for a single
2561+ # line only and send it directly to the server. A server won't
2562+ # progress until it gets a "reply" or "signal" message, but can
2563+ # process "complete" requests while waiting for the reply.
2564+ pass
2565+ case {
2566+ "completions" : list (completions )
2567+ } if all (isinstance (c , str ) for c in completions ):
2568+ # Return valid completions for a client's "complete" request.
2569+ pass
2570+ case {
2571+ "command_list" : list (command_list )
2572+ } if all (isinstance (c , str ) for c in command_list ):
2573+ # Report the list of legal PDB commands to the client.
2574+ # Due to aliases this list is not static, but the client
2575+ # needs to know it for multi-line editing.
2576+ pass
2577+ case _:
2578+ raise AssertionError (
2579+ f"PDB message doesn't follow the schema! { msg } "
2580+ )
2581+
2582+ def _send (self , ** kwargs ):
2583+ self ._ensure_valid_message (kwargs )
25432584 json_payload = json .dumps (kwargs )
25442585 try :
25452586 self ._sockfile .write (json_payload .encode () + b"\n " )
@@ -2774,6 +2815,51 @@ def __init__(self, pid, sockfile, interrupt_script):
27742815 self .pdb_commands = set ()
27752816 self .completion_matches = []
27762817 self .state = "dumb"
2818+ self .write_failed = False
2819+
2820+ def _ensure_valid_message (self , msg ):
2821+ # Ensure the message conforms to our protocol.
2822+ # If anything needs to be changed here for a patch release of Python,
2823+ # the 'revision' in protocol_version() should be updated.
2824+ match msg :
2825+ case {"reply" : str ()}:
2826+ # Send input typed by a user at a prompt to the remote PDB.
2827+ pass
2828+ case {"signal" : "EOF" }:
2829+ # Tell the remote PDB that the user pressed ^D at a prompt.
2830+ pass
2831+ case {"signal" : "INT" }:
2832+ # Tell the remote PDB that the user pressed ^C at a prompt.
2833+ pass
2834+ case {
2835+ "complete" : {
2836+ "text" : str (),
2837+ "line" : str (),
2838+ "begidx" : int (),
2839+ "endidx" : int (),
2840+ }
2841+ }:
2842+ # Ask the remote PDB what completions are valid for the given
2843+ # parameters, using readline's completion protocol.
2844+ pass
2845+ case _:
2846+ raise AssertionError (
2847+ f"PDB message doesn't follow the schema! { msg } "
2848+ )
2849+
2850+ def _send (self , ** kwargs ):
2851+ self ._ensure_valid_message (kwargs )
2852+ json_payload = json .dumps (kwargs )
2853+ try :
2854+ self .sockfile .write (json_payload .encode () + b"\n " )
2855+ self .sockfile .flush ()
2856+ except OSError :
2857+ # This means that the client has abruptly disconnected, but we'll
2858+ # handle that the next time we try to read from the client instead
2859+ # of trying to handle it from everywhere _send() may be called.
2860+ # Track this with a flag rather than assuming readline() will ever
2861+ # return an empty string because the socket may be half-closed.
2862+ self .write_failed = True
27772863
27782864 def read_command (self , prompt ):
27792865 reply = input (prompt )
@@ -2829,7 +2915,7 @@ def readline_completion(self, completer):
28292915
28302916 def cmdloop (self ):
28312917 with self .readline_completion (self .complete ):
2832- while True :
2918+ while not self . write_failed :
28332919 try :
28342920 if not (payload_bytes := self .sockfile .readline ()):
28352921 break
@@ -2889,8 +2975,7 @@ def prompt_for_reply(self, prompt):
28892975 print ("***" , msg , flush = True )
28902976 continue
28912977
2892- self .sockfile .write ((json .dumps (payload ) + "\n " ).encode ())
2893- self .sockfile .flush ()
2978+ self ._send (** payload )
28942979 return
28952980
28962981 def complete (self , text , state ):
@@ -2916,10 +3001,8 @@ def complete(self, text, state):
29163001 }
29173002 }
29183003
2919- try :
2920- self .sockfile .write ((json .dumps (msg ) + "\n " ).encode ())
2921- self .sockfile .flush ()
2922- except OSError :
3004+ self ._send (** msg )
3005+ if self .write_failed :
29233006 return None
29243007
29253008 payload = self .sockfile .readline ()
0 commit comments