Like `subprocess.run`, but with the ability to pipe `stdout` and/or `stderr` to a `Logger` or capture each stream independently while the process is running.
Project description
run_with_logger
Like subprocess.run, but with the ability to pipe stdout and/or stderr to a Logger or capture each stream
independently while the process is running.
What it can do
- Run local commands.
- Run remote commands over SSH.
- For each output stream (
stdoutandstderr), choose independently whether to log, capture, or discard it. - Pipe bytes or files to
stdin. - Pass environment variables to the subprocess (even via SSH if the server's
AcceptEnvsetting allows it). - Enforce configurable timeouts for local subprocesses, remote SSH commands, and SSH network operations.
- Support many of the same arguments as
subprocess.run. - Provide access to the running process and incrementally captured output via a context manager.
Examples
Log stderr and capture stdout
The main process will wait for the subprocess while:
- forwarding
stderrline by line to the logger - capturing
stdoutin aCompletedProcessobject, which is returned when the process finishes
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["foo", "bar"],
logger=logger,
stdout_action="capture",
stderr_action="log",
check=False,
)
print(completed.stdout)
Log stdout and discard stderr
The main process will wait for the subprocess while:
- forwarding
stdoutline by line to the logger - discarding
stderr
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["foo", "bar"],
logger=logger,
stdout_action="log",
stderr_action="discard",
check=False,
)
Capture both streams
Use "capture" for both streams when you want the final CompletedProcess to contain bytes from stdout and stderr.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "out"
print(completed.stderr.decode().strip()) # "err"
Choose the log level and decoding
from logging import INFO, getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
run_with_logger(
args=["python", "-c", "print('hello')"],
logger=logger,
level=INFO,
encoding="utf-8",
stdout_action="log",
stderr_action="discard",
)
Send bytes to stdin
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print(sys.stdin.read().upper())"],
logger=logger,
stdin_data=b"hello",
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "HELLO"
Pipe a file to stdin
from logging import getLogger
from pathlib import Path
from run_with_logger import run_with_logger
logger = getLogger(__name__)
input_path = Path("input.txt")
with input_path.open("rb") as stdin:
completed = run_with_logger(
args=["python", "-c", "import sys; print(sys.stdin.read())"],
logger=logger,
stdin_io=stdin,
stdout_action="capture",
stderr_action="capture",
)
Add environment variables
extra_env is merged with the current environment.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import os; print(os.environ['APP_MODE'])"],
logger=logger,
extra_env={"APP_MODE": "production"},
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip()) # "production"
Use a shell command
Like subprocess.Popen, pass a string and shell=True when you intentionally want shell parsing.
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args="printf 'hello from the shell\n'",
shell=True,
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
Handle non-zero exit codes
With the default check=True, non-zero exits raise CalledProcessError.
Captured streams are attached to the exception.
from logging import getLogger
from subprocess import CalledProcessError
from run_with_logger import run_with_logger
logger = getLogger(__name__)
try:
run_with_logger(
args=["python", "-c", "import sys; print('bad'); sys.exit(2)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
except CalledProcessError as error:
print(error.returncode) # 2
print(error.output.decode()) # captured stdout
Set check=False when you want to inspect the return code yourself:
from logging import getLogger
from run_with_logger import run_with_logger
logger = getLogger(__name__)
completed = run_with_logger(
args=["python", "-c", "import sys; print('bad'); sys.exit(2)"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
check=False,
)
print(completed.returncode) # 2
print(completed.stdout.decode()) # captured stdout
Inspect a running process
Use run_with_logger__cm if you need access to the Popen object while the process is still running.
Captured streams are available as BytesIO buffers.
import time
from io import BytesIO
from logging import getLogger
from run_with_logger import run_with_logger__cm
logger = getLogger(__name__)
with run_with_logger__cm(
args=["python", "-uc", "import time; print('one'); time.sleep(1); print('two')"],
logger=logger,
stdout_action="capture",
stderr_action="capture",
) as info:
stdout_buffer = info["stdout_buffer"]
assert isinstance(stdout_buffer, BytesIO)
while info["process"].poll() is None:
partial_stdout = stdout_buffer.getvalue()
# Do something with partial_stdout while the process runs.
time.sleep(0.1)
completed = info["completed"]
Run a remote command over SSH
Install SSH support first:
python -m pip install "run_with_logger[ssh]"
Then pass either a connected paramiko.SSHClient or a fabric.Connection.
from contextlib import closing
from logging import getLogger
from paramiko import SSHClient
from run_with_logger import run_with_logger__ssh
logger = getLogger(__name__)
with closing(SSHClient()) as client:
# Configure and connect the client as appropriate for your environment.
completed = run_with_logger__ssh(
client=client,
command="whoami",
logger=logger,
stdout_action="capture",
stderr_action="capture",
)
print(completed.stdout.decode().strip())
run_with_logger__ssh__cm provides the same context-manager pattern for SSH channels.
Project details
Release history Release notifications | RSS feed
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 run_with_logger-1.5.0.tar.gz.
File metadata
- Download URL: run_with_logger-1.5.0.tar.gz
- Upload date:
- Size: 22.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c345970272ae81ded9da6d824315e41f17f50b500211b1dccaa80036366d094
|
|
| MD5 |
3589955985f13c7605acbd0d0d472bfd
|
|
| BLAKE2b-256 |
5ecef8c4edcc8cc663aeda34d8f32a9ba9d89c14148af7f5ae5b5ee8ae0220b5
|
Provenance
The following attestation bundles were made for run_with_logger-1.5.0.tar.gz:
Publisher:
publish.yml on rudolfbyker/run_with_logger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
run_with_logger-1.5.0.tar.gz -
Subject digest:
5c345970272ae81ded9da6d824315e41f17f50b500211b1dccaa80036366d094 - Sigstore transparency entry: 1733061652
- Sigstore integration time:
-
Permalink:
rudolfbyker/run_with_logger@e4f019bf13157ea729daf0f2bd66ae3a8e230699 -
Branch / Tag:
refs/tags/1.5.0 - Owner: https://github.com/rudolfbyker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4f019bf13157ea729daf0f2bd66ae3a8e230699 -
Trigger Event:
release
-
Statement type:
File details
Details for the file run_with_logger-1.5.0-py3-none-any.whl.
File metadata
- Download URL: run_with_logger-1.5.0-py3-none-any.whl
- Upload date:
- Size: 14.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56470d71cc0d2abd84fade5084960d7a39f7f49c3a957c49cdd9ad3f81e82839
|
|
| MD5 |
0eb4c8eeb965c2a354e84b29932d7882
|
|
| BLAKE2b-256 |
36ef35338c3117a74a49d8b9411f4e60eb3b44529e843624a08d0df7eb6779af
|
Provenance
The following attestation bundles were made for run_with_logger-1.5.0-py3-none-any.whl:
Publisher:
publish.yml on rudolfbyker/run_with_logger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
run_with_logger-1.5.0-py3-none-any.whl -
Subject digest:
56470d71cc0d2abd84fade5084960d7a39f7f49c3a957c49cdd9ad3f81e82839 - Sigstore transparency entry: 1733061672
- Sigstore integration time:
-
Permalink:
rudolfbyker/run_with_logger@e4f019bf13157ea729daf0f2bd66ae3a8e230699 -
Branch / Tag:
refs/tags/1.5.0 - Owner: https://github.com/rudolfbyker
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e4f019bf13157ea729daf0f2bd66ae3a8e230699 -
Trigger Event:
release
-
Statement type: