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.0.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.0-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: subprocess_multitee-0.2.0.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.0.tar.gz
Algorithm Hash digest
SHA256 ce9bc291b5d14d7e483d2f893541bdcef5801c6f4d0b335de3473c79b920ff1f
MD5 6bd770aab82c3725c7d1a5bb1dbf54b7
BLAKE2b-256 1f8b298b0fccd6f9edd6d069e3d4f5f42caaac29299d4e744c43969eb2587ba0

See more details on using hashes here.

File details

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

File metadata

  • Download URL: subprocess_multitee-0.2.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 841f5bec6ba9163dc4f277db8a92bf7eeb2762e9b999d2ff8ec86a3e9f1e3634
MD5 b91774f3fa9f54265491cb7c67a65105
BLAKE2b-256 31aee869c62191447e9da14094769a29365ace35bbf58b341426f1919ae00702

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