@@ -110,6 +110,7 @@ def __init__(
110110 ]] = None ,
111111 respawn : bool = False ,
112112 respawn_delay : Optional [float ] = None ,
113+ cached_output : bool = False ,
113114 ** kwargs
114115 ) -> None :
115116 """
@@ -208,6 +209,8 @@ def __init__(
208209 :param: respawn if 'True', relaunch the process that abnormally died.
209210 Defaults to 'False'.
210211 :param: respawn_delay a delay time to relaunch the died process if respawn is 'True'.
212+ :param: cached_output if `True`, both stdout and stderr will be cached.
213+ Use get_stdout() and get_stderr() to read the buffered output.
211214 """
212215 super ().__init__ (** kwargs )
213216 self .__cmd = [normalize_to_list_of_substitutions (x ) for x in cmd ]
@@ -254,6 +257,7 @@ def __init__(
254257 self .__sigkill_timer = None # type: Optional[TimerAction]
255258 self .__stdout_buffer = io .StringIO ()
256259 self .__stderr_buffer = io .StringIO ()
260+ self .__cached_output = cached_output
257261
258262 self .__executed = False
259263
@@ -505,59 +509,32 @@ def __on_process_stdin(
505509 cast (ProcessStdin , event )
506510 return None
507511
508- def __on_process_stdout (
509- self , event : ProcessIO
512+ def __on_process_output (
513+ self , event : ProcessIO , buffer , logger
510514 ) -> Optional [SomeActionsType ]:
511515 to_write = event .text .decode (errors = 'replace' )
512- if self . __stdout_buffer .closed :
513- # __stdout_buffer was probably closed by __flush_buffers on shutdown. Output without
516+ if buffer .closed :
517+ # buffer was probably closed by __flush_buffers on shutdown. Output without
514518 # buffering.
515- self .__stdout_logger .info (
516- self .__output_format .format (line = to_write , this = self )
517- )
518- else :
519- self .__stdout_buffer .write (to_write )
520- self .__stdout_buffer .seek (0 )
521- last_line = None
522- for line in self .__stdout_buffer :
523- if line .endswith (os .linesep ):
524- self .__stdout_logger .info (
525- self .__output_format .format (line = line [:- len (os .linesep )], this = self )
526- )
527- else :
528- last_line = line
529- break
530- self .__stdout_buffer .seek (0 )
531- self .__stdout_buffer .truncate (0 )
532- if last_line is not None :
533- self .__stdout_buffer .write (last_line )
534-
535- def __on_process_stderr (
536- self , event : ProcessIO
537- ) -> Optional [SomeActionsType ]:
538- to_write = event .text .decode (errors = 'replace' )
539- if self .__stderr_buffer .closed :
540- # __stderr buffer was probably closed by __flush_buffers on shutdown. Output without
541- # buffering.
542- self .__stderr_logger .info (
519+ buffer .info (
543520 self .__output_format .format (line = to_write , this = self )
544521 )
545522 else :
546- self . __stderr_buffer .write (to_write )
547- self . __stderr_buffer .seek (0 )
523+ buffer .write (to_write )
524+ buffer .seek (0 )
548525 last_line = None
549- for line in self . __stderr_buffer :
526+ for line in buffer :
550527 if line .endswith (os .linesep ):
551- self . __stderr_logger .info (
528+ logger .info (
552529 self .__output_format .format (line = line [:- len (os .linesep )], this = self )
553530 )
554531 else :
555532 last_line = line
556533 break
557- self . __stderr_buffer .seek (0 )
558- self . __stderr_buffer .truncate (0 )
534+ buffer .seek (0 )
535+ buffer .truncate (0 )
559536 if last_line is not None :
560- self . __stderr_buffer .write (last_line )
537+ buffer .write (last_line )
561538
562539 def __flush_buffers (self , event , context ):
563540 line = self .__stdout_buffer .getvalue ()
@@ -583,6 +560,35 @@ def __flush_buffers(self, event, context):
583560 self .__stderr_buffer .seek (0 )
584561 self .__stderr_buffer .truncate (0 )
585562
563+ def __on_process_output_cached (
564+ self , event : ProcessIO , buffer , logger
565+ ) -> Optional [SomeActionsType ]:
566+ to_write = event .text .decode (errors = 'replace' )
567+ last_cursor = buffer .tell ()
568+ self .__stdout_buffer .seek (0 , 2 ) # go to end of buffer
569+ buffer .write (to_write )
570+ buffer .seek (last_cursor )
571+ new_cursor = last_cursor
572+ for line in buffer :
573+ if not line .endswith (os .linesep ):
574+ break
575+ new_cursor = buffer .tell ()
576+ logger .info (
577+ self .__output_format .format (line = line [:- len (os .linesep )], this = self )
578+ )
579+ buffer .seek (new_cursor )
580+
581+ def __flush_cached_buffers (self , event , context ):
582+ for line in self .__stdout_buffer :
583+ self .__stdout_buffer .info (
584+ self .__output_format .format (line = line , this = self )
585+ )
586+
587+ for line in self .__stderr_buffer :
588+ self .__stderr_logger .info (
589+ self .__output_format .format (line = line , this = self )
590+ )
591+
586592 def __on_shutdown (self , event : Event , context : LaunchContext ) -> Optional [SomeActionsType ]:
587593 due_to_sigint = cast (Shutdown , event ).due_to_sigint
588594 return self ._shutdown_process (
@@ -817,6 +823,13 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
817823 # If shutdown starts before execution can start, don't start execution.
818824 return None
819825
826+ if self .__cached_output :
827+ on_output_method = self .__on_process_output_cached
828+ flush_buffers_method = self .__flush_cached_buffers
829+ else :
830+ on_output_method = self .__on_process_output
831+ flush_buffers_method = self .__flush_buffers
832+
820833 event_handlers = [
821834 EventHandler (
822835 matcher = lambda event : is_a_subclass (event , ShutdownProcess ),
@@ -829,8 +842,10 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
829842 OnProcessIO (
830843 target_action = self ,
831844 on_stdin = self .__on_process_stdin ,
832- on_stdout = self .__on_process_stdout ,
833- on_stderr = self .__on_process_stderr
845+ on_stdout = lambda event : on_output_method (
846+ event , self .__stdout_buffer , self .__stdout_logger ),
847+ on_stderr = lambda event : on_output_method (
848+ event , self .__stderr_buffer , self .__stderr_logger ),
834849 ),
835850 OnShutdown (
836851 on_shutdown = self .__on_shutdown ,
@@ -841,7 +856,7 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
841856 ),
842857 OnProcessExit (
843858 target_action = self ,
844- on_exit = self . __flush_buffers ,
859+ on_exit = flush_buffers_method ,
845860 ),
846861 ]
847862 for event_handler in event_handlers :
@@ -899,3 +914,30 @@ def shell(self):
899914 def prefix (self ):
900915 """Getter for prefix."""
901916 return self .__prefix
917+
918+ def get_stdout (self ):
919+ """
920+ Get cached stdout.
921+
922+ :raises RuntimeError: if cached_output is false.
923+ """
924+ if not self .__cached_output :
925+ raise RuntimeError (f"cached output must be true to be able to get stdout, proc '{ self .__name } '" )
926+ return self .__stdout_buffer .getvalue ()
927+
928+ def get_stderr (self ):
929+ """
930+ Get cached stdout.
931+
932+ :raises RuntimeError: if cached_output is false.
933+ """
934+ if not self .__cached_output :
935+ raise RuntimeError (f"cached output must be true to be able to get stderr, proc '{ self .__name } '" )
936+ return self .__stderr_buffer .getvalue ()
937+
938+ @property
939+ def return_code (self ):
940+ """Gets the process return code, None if it hasn't finished."""
941+ if self ._subprocess_transport is None :
942+ return None
943+ return self ._subprocess_transport .get_returncode ()
0 commit comments