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.1.1.tar.gz (30.6 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.1.1-py3-none-any.whl (7.8 kB view details)

Uploaded Python 3

File details

Details for the file subprocess_multitee-0.1.1.tar.gz.

File metadata

  • Download URL: subprocess_multitee-0.1.1.tar.gz
  • Upload date:
  • Size: 30.6 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.1.1.tar.gz
Algorithm Hash digest
SHA256 b34890d4a7cf49366eba53f2b18a16207c28e2ac407465d5246d83f02d00cc6c
MD5 de81f751ddf22023dbfa07a986560c28
BLAKE2b-256 6863bd99114c559c183a7fd314102ff9a6847ba6aa871646af3b454d6a65f753

See more details on using hashes here.

File details

Details for the file subprocess_multitee-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: subprocess_multitee-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.8 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.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7f2259bbdd48c3f67fad98ce1ad438cf9c3c8d43840350dfb1a3cd459aa87eaf
MD5 66e9327b509297593662e08436f0d121
BLAKE2b-256 eb257c8e9a30fc3506b6cb8f0a5c8458c48df33eeae9dafaee395edd6ebe75b1

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