@@ -196,6 +196,7 @@ def __init__(
196196 ]] = None ,
197197 respawn : bool = False ,
198198 respawn_delay : Optional [float ] = None ,
199+ cached_output : bool = False ,
199200 ** kwargs
200201 ) -> None :
201202 """
@@ -294,6 +295,8 @@ def __init__(
294295 :param: respawn if 'True', relaunch the process that abnormally died.
295296 Defaults to 'False'.
296297 :param: respawn_delay a delay time to relaunch the died process if respawn is 'True'.
298+ :param: cached_output if `True`, both stdout and stderr will be cached.
299+ Use get_stdout() and get_stderr() to read the buffered output.
297300 """
298301 super ().__init__ (** kwargs )
299302 self .__cmd = [normalize_to_list_of_substitutions (x ) for x in cmd ]
@@ -340,6 +343,7 @@ def __init__(
340343 self .__sigkill_timer = None # type: Optional[TimerAction]
341344 self .__stdout_buffer = io .StringIO ()
342345 self .__stderr_buffer = io .StringIO ()
346+ self .__cached_output = cached_output
343347
344348 self .__executed = False
345349
@@ -591,59 +595,32 @@ def __on_process_stdin(
591595 cast (ProcessStdin , event )
592596 return None
593597
594- def __on_process_stdout (
595- self , event : ProcessIO
598+ def __on_process_output (
599+ self , event : ProcessIO , buffer , logger
596600 ) -> Optional [SomeActionsType ]:
597601 to_write = event .text .decode (errors = 'replace' )
598- if self . __stdout_buffer .closed :
599- # __stdout_buffer was probably closed by __flush_buffers on shutdown. Output without
602+ if buffer .closed :
603+ # buffer was probably closed by __flush_buffers on shutdown. Output without
600604 # buffering.
601- self .__stdout_logger .info (
602- self .__output_format .format (line = to_write , this = self )
603- )
604- else :
605- self .__stdout_buffer .write (to_write )
606- self .__stdout_buffer .seek (0 )
607- last_line = None
608- for line in self .__stdout_buffer :
609- if line .endswith (os .linesep ):
610- self .__stdout_logger .info (
611- self .__output_format .format (line = line [:- len (os .linesep )], this = self )
612- )
613- else :
614- last_line = line
615- break
616- self .__stdout_buffer .seek (0 )
617- self .__stdout_buffer .truncate (0 )
618- if last_line is not None :
619- self .__stdout_buffer .write (last_line )
620-
621- def __on_process_stderr (
622- self , event : ProcessIO
623- ) -> Optional [SomeActionsType ]:
624- to_write = event .text .decode (errors = 'replace' )
625- if self .__stderr_buffer .closed :
626- # __stderr buffer was probably closed by __flush_buffers on shutdown. Output without
627- # buffering.
628- self .__stderr_logger .info (
605+ buffer .info (
629606 self .__output_format .format (line = to_write , this = self )
630607 )
631608 else :
632- self . __stderr_buffer .write (to_write )
633- self . __stderr_buffer .seek (0 )
609+ buffer .write (to_write )
610+ buffer .seek (0 )
634611 last_line = None
635- for line in self . __stderr_buffer :
612+ for line in buffer :
636613 if line .endswith (os .linesep ):
637- self . __stderr_logger .info (
614+ logger .info (
638615 self .__output_format .format (line = line [:- len (os .linesep )], this = self )
639616 )
640617 else :
641618 last_line = line
642619 break
643- self . __stderr_buffer .seek (0 )
644- self . __stderr_buffer .truncate (0 )
620+ buffer .seek (0 )
621+ buffer .truncate (0 )
645622 if last_line is not None :
646- self . __stderr_buffer .write (last_line )
623+ buffer .write (last_line )
647624
648625 def __flush_buffers (self , event , context ):
649626 line = self .__stdout_buffer .getvalue ()
@@ -669,6 +646,35 @@ def __flush_buffers(self, event, context):
669646 self .__stderr_buffer .seek (0 )
670647 self .__stderr_buffer .truncate (0 )
671648
649+ def __on_process_output_cached (
650+ self , event : ProcessIO , buffer , logger
651+ ) -> Optional [SomeActionsType ]:
652+ to_write = event .text .decode (errors = 'replace' )
653+ last_cursor = buffer .tell ()
654+ self .__stdout_buffer .seek (0 , 2 ) # go to end of buffer
655+ buffer .write (to_write )
656+ buffer .seek (last_cursor )
657+ new_cursor = last_cursor
658+ for line in buffer :
659+ if not line .endswith (os .linesep ):
660+ break
661+ new_cursor = buffer .tell ()
662+ logger .info (
663+ self .__output_format .format (line = line [:- len (os .linesep )], this = self )
664+ )
665+ buffer .seek (new_cursor )
666+
667+ def __flush_cached_buffers (self , event , context ):
668+ for line in self .__stdout_buffer :
669+ self .__stdout_buffer .info (
670+ self .__output_format .format (line = line , this = self )
671+ )
672+
673+ for line in self .__stderr_buffer :
674+ self .__stderr_logger .info (
675+ self .__output_format .format (line = line , this = self )
676+ )
677+
672678 def __on_shutdown (self , event : Event , context : LaunchContext ) -> Optional [SomeActionsType ]:
673679 due_to_sigint = cast (Shutdown , event ).due_to_sigint
674680 return self ._shutdown_process (
@@ -903,6 +909,13 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
903909 # If shutdown starts before execution can start, don't start execution.
904910 return None
905911
912+ if self .__cached_output :
913+ on_output_method = self .__on_process_output_cached
914+ flush_buffers_method = self .__flush_cached_buffers
915+ else :
916+ on_output_method = self .__on_process_output
917+ flush_buffers_method = self .__flush_buffers
918+
906919 event_handlers = [
907920 EventHandler (
908921 matcher = lambda event : is_a_subclass (event , ShutdownProcess ),
@@ -915,8 +928,10 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
915928 OnProcessIO (
916929 target_action = self ,
917930 on_stdin = self .__on_process_stdin ,
918- on_stdout = self .__on_process_stdout ,
919- on_stderr = self .__on_process_stderr
931+ on_stdout = lambda event : on_output_method (
932+ event , self .__stdout_buffer , self .__stdout_logger ),
933+ on_stderr = lambda event : on_output_method (
934+ event , self .__stderr_buffer , self .__stderr_logger ),
920935 ),
921936 OnShutdown (
922937 on_shutdown = self .__on_shutdown ,
@@ -927,7 +942,7 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
927942 ),
928943 OnProcessExit (
929944 target_action = self ,
930- on_exit = self . __flush_buffers ,
945+ on_exit = flush_buffers_method ,
931946 ),
932947 ]
933948 for event_handler in event_handlers :
@@ -985,3 +1000,30 @@ def shell(self):
9851000 def prefix (self ):
9861001 """Getter for prefix."""
9871002 return self .__prefix
1003+
1004+ def get_stdout (self ):
1005+ """
1006+ Get cached stdout.
1007+
1008+ :raises RuntimeError: if cached_output is false.
1009+ """
1010+ if not self .__cached_output :
1011+ raise RuntimeError (f"cached output must be true to be able to get stdout, proc '{ self .__name } '" )
1012+ return self .__stdout_buffer .getvalue ()
1013+
1014+ def get_stderr (self ):
1015+ """
1016+ Get cached stdout.
1017+
1018+ :raises RuntimeError: if cached_output is false.
1019+ """
1020+ if not self .__cached_output :
1021+ raise RuntimeError (f"cached output must be true to be able to get stderr, proc '{ self .__name } '" )
1022+ return self .__stderr_buffer .getvalue ()
1023+
1024+ @property
1025+ def return_code (self ):
1026+ """Gets the process return code, None if it hasn't finished."""
1027+ if self ._subprocess_transport is None :
1028+ return None
1029+ return self ._subprocess_transport .get_returncode ()
0 commit comments