import os import sys from typing import no_type_check class FdRedirect: def __init__(self, file_prefix: str, fd: int): fname = os.path.join("/tmp", f"{file_prefix}.{fd}") if os.path.exists(fname): os.unlink(fname) self.dest_fd = os.open(fname, os.O_WRONLY | os.O_CREAT) self.dest_fname = fname self.source_fd = fd os.set_inheritable(self.dest_fd, True) def __del__(self): os.close(self.dest_fd) def purge(self) -> None: os.unlink(self.dest_fname) def read(self) -> bytes: with open(self.dest_fname, "rb") as f: return f.read() or b"" def link(self) -> None: os.dup2(self.dest_fd, self.source_fd) class ProcessOutputCapture: def __init__(self, proc_name: str, prefix: str): prefix = f"{proc_name}_{prefix}" self.stdout_redirect = FdRedirect(prefix, 1) self.stderr_redirect = FdRedirect(prefix, 2) def __del__(self): self.stdout_redirect.purge() self.stderr_redirect.purge() @no_type_check # ipython classes have incompatible signatures def link_with_current_proc(self) -> None: try: # prevent ipykernel from redirecting stdout/stderr of python subprocesses from ipykernel.iostream import OutStream if isinstance(sys.stdout, OutStream): sys.stdout = sys.__stdout__ if isinstance(sys.stderr, OutStream): sys.stderr = sys.__stderr__ except ImportError: pass # link stdout/stderr to the fifo self.stdout_redirect.link() self.stderr_redirect.link() def read_outerr(self) -> tuple[str, str]: out_str = self.stdout_redirect.read().decode() err_str = self.stderr_redirect.read().decode() return out_str, err_str