Skip to main content

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 .pipes list or Popen.stdout)
  • DEVNULL — discard output (no-op, skipped efficiently)
  • File objects — anything with .write() (binary mode recommended)
  • File descriptors — raw int fds
  • Text-mode files — automatically uses .buffer if 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 PIPE destination 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

  1. tee() creates an os.pipe()
  2. Returns a object with fileno() pointing to the write end
  3. Spawns a daemon thread that reads 1 byte at a time and writes to all destinations
  4. Popen passes the fd to the subprocess, then closes the parent's copy
  5. When subprocess exits, the pipe closes, thread sees EOF and exits

Alternatives

  • subprocess-tee — Drop-in replacement for subprocess.run that prints output in real-time while capturing. Simple API (tee=False to disable), but implies universal_newlines=True (text mode only) and doesn't support multiple arbitrary destinations.

  • tee-subprocess — Also replaces subprocess.run with tee support, but adds async/await support via asyncio. 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

subprocess_multitee-0.2.1.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

subprocess_multitee-0.2.1-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

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

Hashes for subprocess_multitee-0.2.1.tar.gz
Algorithm Hash digest
SHA256 69343c2f3db64b207db590eec0f7184048f8ebc342ab9b385f84fcd396e906e1
MD5 9393e13d3d8f1e7b0302e494376e8f03
BLAKE2b-256 692bf12e452b78931eaa6e1595bacf84836d7731689c18e0bec8fd7bd10834b3

See more details on using hashes here.

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

Hashes for subprocess_multitee-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2e4b309a24dcc507d52a91cffc16205c046cb62849ee7bf939aa13af82647f6f
MD5 a3ec8dff093693a79e0d094a7d75955d
BLAKE2b-256 077d15540139bc4c9bc78883412a1fd175dcd3c6517f9ecdd5ac9d4db01896b4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page