STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
capture.py
Go to the documentation of this file.
1"""Capture what one thread prints, without touching any other thread's output.
2
3``contextlib.redirect_stdout`` swaps ``sys.stdout`` for the whole process. In the API, burns run on
4daemon threads two at a time: two overlapping flights restored each other's buffers, and the
5server went on printing into a StringIO for ever while other requests' prints landed in a run's
6notes. Here ``sys.stdout`` is replaced once by a router that sends a thread's writes to that
7thread's buffer while it is capturing, and everything else to the real stream.
8"""
9
10from __future__ import annotations
11
12import contextlib
13import io
14import sys
15import threading
16from typing import Iterator
17
18_local = threading.local()
19_install_lock = threading.Lock()
20
21
22class _ThreadRouter(io.TextIOBase):
23 def __init__(self, real: object) -> None:
24 self.real = real
25
26 def _target(self) -> object:
27 return getattr(_local, "buffer", None) or self.real
28
29 def write(self, s: str) -> int: # type: ignore[override]
30 return self._target().write(s) # type: ignore[attr-defined]
31
32 def flush(self) -> None:
33 target = self._target()
34 if hasattr(target, "flush"):
35 target.flush() # type: ignore[attr-defined]
36
37 def isatty(self) -> bool:
38 return bool(getattr(self.real, "isatty", lambda: False)())
39
40 @property
41 def encoding(self) -> str: # type: ignore[override]
42 return getattr(self.real, "encoding", "utf-8")
43
44
45def _install() -> None:
46 with _install_lock:
47 if not isinstance(sys.stdout, _ThreadRouter):
48 sys.stdout = _ThreadRouter(sys.stdout)
49
50
51@contextlib.contextmanager
52def thread_stdout() -> Iterator[io.StringIO]:
53 """Everything this thread prints inside the block, and nothing any other thread prints."""
54 _install()
55 previous = getattr(_local, "buffer", None)
56 buffer = io.StringIO()
57 _local.buffer = buffer
58 try:
59 yield buffer
60 finally:
61 _local.buffer = previous
None __init__(self, object real)
Definition capture.py:23
Iterator[io.StringIO] thread_stdout()
Definition capture.py:52