A small `tee` function for splitting stdout/stderr in subprocess, and a `subprocess.Popen` convenience wrapper
Project description
subprocess-multitee
Multiplex subprocess stdout/stderr to multiple destinations simultaneously.
from subprocess_multitee import Popen, tee, PIPE
import sys
# Capture output AND print to terminal in real-time
proc = Popen(['make'], stdout=tee(PIPE, sys.stdout))
output = proc.stdout.read()
proc.wait()
Installation
pip install subprocess-multitee
Why?
Standard subprocess forces a choice: either capture output (stdout=PIPE) or display it (stdout=sys.stdout). This library lets you do both—plus log to files, discard with DEVNULL, or any combination.
The tee() function creates a pipe that spawns a background thread reading one byte at a time and writing to all destinations, ensuring real-time output even for line-buffered programs.
Usage
Basic: Capture + Display
from subprocess_multitee import Popen, tee, PIPE
import sys
proc = Popen(['python', 'train.py'], stdout=tee(PIPE, sys.stdout))
output = proc.stdout.read() # Full output captured
proc.wait() # Terminal saw it in real-time
Log to File + Display + Capture
with open('build.log', 'wb') as log:
proc = Popen(['make', '-j4'],
stdout=tee(PIPE, sys.stdout, log),
stderr=tee(PIPE, sys.stderr, log))
stdout, stderr = proc.communicate()
With run() Helper
from subprocess_multitee import run, tee, PIPE
import sys
result = run(['pytest', '-v'], stdout=tee(PIPE, sys.stdout), check=True)
print(f"Captured {len(result.stdout)} bytes")
Discard + Capture (silent but recorded)
from subprocess_multitee import Popen, tee, PIPE, DEVNULL
proc = Popen(['noisy-command'], stdout=tee(DEVNULL, PIPE))
output = proc.stdout.read() # Captured, but nothing printed
With stdlib Popen (manual cleanup)
import subprocess
from subprocess_multitee import tee, PIPE
t = tee(PIPE, open('log.txt', 'wb'))
proc = subprocess.Popen(['cmd'], stdout=t)
proc.wait()
t.close() # Required with stdlib Popen
output = t.pipes[0].read()
API
tee(*destinations) -> _Tee
Creates a tee object for use as stdout or stderr in Popen.
Destinations can be any combination of:
PIPE— creates a readable pipe (accessible via.pipeslist orPopen.stdout)DEVNULL— discard output (no-op, skipped efficiently)- File objects — anything with
.write()(binary mode recommended) - File descriptors — raw
intfds - Text-mode files — automatically uses
.bufferif available
Not supported: STDOUT (use stderr=tee(...) separately)
Popen
Drop-in subclass of subprocess.Popen with tee integration:
- Automatically closes parent's write fd after fork
- Exposes first
PIPEdestination as.stdout/.stderr - Joins tee threads on context manager exit
run(), call(), check_call(), check_output()
Drop-in replacements using the enhanced Popen.
How It Works
tee()creates anos.pipe()- Returns a object with
fileno()pointing to the write end - Spawns a daemon thread that reads 1 byte at a time and writes to all destinations
Popenpasses the fd to the subprocess, then closes the parent's copy- When subprocess exits, the pipe closes, thread sees EOF and exits
Alternatives
-
subprocess-tee — Drop-in replacement for
subprocess.runthat prints output in real-time while capturing. Simple API (tee=Falseto disable), but impliesuniversal_newlines=True(text mode only) and doesn't support multiple arbitrary destinations. -
tee-subprocess — Also replaces
subprocess.runwith tee support, but adds async/await support viaasyncio. Automatically detects async context. Supports redirecting tee output to file objects. Better static typing. More complex internals.
subprocess-multitee takes a different approach: a composable tee(*destinations) primitive that works with any number of destinations (multiple files, PIPE, DEVNULL, etc.) and a minimal Popen subclass. This gives more flexibility at the cost of slightly more verbose usage.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file subprocess_multitee-0.2.1.tar.gz.
File metadata
- Download URL: subprocess_multitee-0.2.1.tar.gz
- Upload date:
- Size: 31.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69343c2f3db64b207db590eec0f7184048f8ebc342ab9b385f84fcd396e906e1
|
|
| MD5 |
9393e13d3d8f1e7b0302e494376e8f03
|
|
| BLAKE2b-256 |
692bf12e452b78931eaa6e1595bacf84836d7731689c18e0bec8fd7bd10834b3
|
File details
Details for the file subprocess_multitee-0.2.1-py3-none-any.whl.
File metadata
- Download URL: subprocess_multitee-0.2.1-py3-none-any.whl
- Upload date:
- Size: 8.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e4b309a24dcc507d52a91cffc16205c046cb62849ee7bf939aa13af82647f6f
|
|
| MD5 |
a3ec8dff093693a79e0d094a7d75955d
|
|
| BLAKE2b-256 |
077d15540139bc4c9bc78883412a1fd175dcd3c6517f9ecdd5ac9d4db01896b4
|