stouputils.ctx.fd_capture module#

Copying into a log what reaches the stdout and stderr file descriptors without going through sys.stdout.

C extensions, os.system and child processes that do not capture their output write straight to the descriptors, so replacing sys.stdout never sees them. FdCapture points the descriptors at a pseudo-terminal, or a pipe when the output is not a terminal, and a thread copies what arrives both to the real terminal and into the log.

READ_SIZE: int = 65536[source]#

Bytes read from a descriptor at once.

class FdChannel(
fd: int,
saved: int,
reader: int,
target: TeeTarget,
decoder: IncrementalDecoder = <factory>,
)[source]#

Bases: object

One redirected descriptor: where it pointed before, and the end the reader thread reads it from.

fd: int[source]#

Descriptor redirected, 1 for stdout or 2 for stderr.

saved: int[source]#

Copy of what the descriptor pointed to before, the real terminal.

reader: int[source]#

Read end of the pseudo-terminal or pipe the descriptor now points to.

target: TeeTarget[source]#

Log output receiving the text, with a line state of its own so stdout and stderr lines never merge.

decoder: IncrementalDecoder[source]#
class FdCapture(
file: IO[Any],
strip_colors: bool,
ignore_lineup: bool,
)[source]#

Bases: object

Copy everything written to the stdout and stderr descriptors into a log file, the terminal still showing it.

Python’s own output reaches the descriptors too, so the log receives every line in the order it was written. The reader thread never stops on an error, since a full pipe would block every later write of the program.

Parameters:
  • file – Log file receiving the text.

  • strip_colors – Whether the log receives the text without ANSI colors.

  • ignore_lineup – Whether the log receives what a terminal ends up showing, see LineState.

channels: list[FdChannel][source]#

The two redirected descriptors, empty while not capturing

thread: Thread | None[source]#

Thread copying what the descriptors receive

static unsupported_reason() → str | None[source]#

Why the descriptors cannot be captured here, None when they can.

start() → None[source]#

Point stdout and stderr at the reader thread.

stop() → None[source]#

Give the descriptors back, then let the reader thread copy what is left before it ends.

redirect(
fd: int,
) → FdChannel[source]#

Point a descriptor at a new pseudo-terminal, or pipe when it is not a terminal, keeping a copy of the old one.

copy_forever() → None[source]#

Copy what each descriptor receives until every writer is gone.

copy_once(
channel: FdChannel,
) → bool[source]#

Copy one read of a descriptor to the terminal and the log.

Returns:

False once nothing writes to the descriptor any more.

flush_all() → None[source]#

Flush Python’s streams and the C library’s, so what they hold goes through the descriptors now.

copy_terminal_settings(
terminal: int,
pseudo_terminal: int,
) → None[source]#

Give a pseudo-terminal the size of the real one, and stop it turning newlines into \r\n.

write_all(fd: int, data: bytes) → None[source]#

Write every byte to a descriptor, which a single os.write does not promise.