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.0.tar.gz (30.5 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.0-py3-none-any.whl (7.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: subprocess_multitee-0.1.0.tar.gz
  • Upload date:
  • Size: 30.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","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.0.tar.gz
Algorithm Hash digest
SHA256 19d58ce09707425d7a4418c3fda3f48b5d0f35b72d3dd55b8081e0e2f9943329
MD5 bb0baad96144a3a55d5c533430870f57
BLAKE2b-256 d8228533ac29c0d1b3e4a38295e141ce8a0f8fd6e4f858c6bb723a8e810ee731

See more details on using hashes here.

File details

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

File metadata

  • Download URL: subprocess_multitee-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8af6118bf4e9c54dfe0c917c4c4126442d38b364e46928c3a5edaf891b9096b4
MD5 af75708b0f690a824c9e316704e3617a
BLAKE2b-256 ba8818d9bc4cee2f4ab891243482ecdfddd7c75f01adfa6956ea29ebfb4ccbde

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