Platform agnostic command and shell execution tool with timeout handling, live output capture, and UAC/sudo privilege elevation
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Helper functions, constants, and utilities that support the core command_runner functionality. Includes encoding conversion, threading support, and platform-specific constants.
Robust text encoding conversion with comprehensive error handling for different platforms and character sets.
def to_encoding(process_output, encoding, errors):
"""
Convert bytes output to string with comprehensive error handling.
Handles the conversion of subprocess output from bytes to strings with
platform-appropriate encoding and error handling strategies. Supports
various encoding scenarios and provides fallback mechanisms.
Args:
process_output (Union[str, bytes]): Raw process output to convert
encoding (Optional[str]): Target encoding string. None bypasses conversion.
False returns original bytes unchanged.
errors (str): Error handling strategy for encoding issues:
'backslashreplace', 'ignore', 'replace', 'strict'
Returns:
str: Converted string output, or original if conversion not needed
Examples:
Convert Windows command output:
>>> output = b'Hello\xff World'
>>> result = to_encoding(output, 'cp437', 'backslashreplace')
>>> print(result) # 'Hello\\xff World'
Handle UTF-8 conversion:
>>> output = b'Caf\xc3\xa9'
>>> result = to_encoding(output, 'utf-8', 'strict')
>>> print(result) # 'Café'
Skip encoding conversion:
>>> output = b'binary data'
>>> result = to_encoding(output, False, 'ignore')
>>> print(type(result)) # <class 'bytes'>
Error Handling:
- Gracefully handles encoding errors with specified strategy
- Converts None output to empty string
- Logs encoding errors for debugging
- Falls back to 'ignore' mode for problematic encodings
Platform Notes:
Windows: Commonly used with 'cp437' encoding for cmd.exe output
Unix/Linux: Typically used with 'utf-8' encoding
macOS: Usually 'utf-8' encoding with some locale variations
"""Decorator and helper functions for asynchronous command execution using Python's concurrent.futures.
def threaded(fn):
"""
Decorator to make any function return a concurrent.Future object.
Converts synchronous functions into asynchronous versions that execute
in background threads and return Future objects for result handling.
Args:
fn: Function to make threaded
Returns:
Wrapped function that returns concurrent.futures.Future
Examples:
Create threaded function:
>>> @threaded
... def long_task(duration):
... time.sleep(duration)
... return f"Completed after {duration} seconds"
>>>
>>> future = long_task(5)
>>> print("Task started...")
>>> result = future.result() # Blocks until complete
>>> print(result)
Force synchronous execution:
>>> future = long_task(5, __no_threads=True) # Executes immediately
Implementation:
Uses concurrent.futures.Future with background thread execution
Available only on Python 3.3+ (graceful fallback on older versions)
Supports special __no_threads keyword to force synchronous execution
"""
def call_with_future(fn, future, args, kwargs):
"""
Execute function with Future result handling.
Helper function for threaded execution that properly handles function
results and exceptions in the Future object.
Args:
fn: Function to execute
future: Future object to store result/exception
args: Positional arguments for function
kwargs: Keyword arguments for function
Returns:
None (result stored in future object)
Implementation:
Called internally by @threaded decorator
Handles both successful results and exceptions
Ensures proper Future state management
"""Launch detached processes with time delays for cleanup and maintenance operations.
def deferred_command(command, defer_time=300):
"""
Launch a command detached from parent process after specified delay.
Creates an independent shell process that waits for the specified time
then executes the command. Useful for self-updating applications, cleanup
operations, or any task that needs to run after the parent process exits.
Args:
command (str): Shell command to execute after delay
defer_time (int): Delay in seconds before execution (default: 300)
Returns:
None
Examples:
Cleanup temporary files after 5 minutes:
>>> deferred_command('rm -rf /tmp/myapp_temp', defer_time=300)
Self-update after application exit:
>>> deferred_command('cp /tmp/newversion /usr/local/bin/myapp', defer_time=10)
Log rotation:
>>> deferred_command('gzip /var/log/myapp.log && mv /var/log/myapp.log.gz /archive/', defer_time=3600)
Platform Implementation:
Windows: Uses 'ping 127.0.0.1 -n {seconds}' as timer, then executes command
Unix/Linux: Uses 'sleep {seconds} && command' pattern
Process Details:
Creates completely detached subprocess with no stdio connections
Parent process can exit safely without affecting deferred command
Command executes in shell context for maximum flexibility
No output capture or error handling (fire-and-forget operation)
"""# Standard subprocess pipe reference
PIPE = subprocess.PIPE
# Platform-specific priority constants (Windows)
BELOW_NORMAL_PRIORITY_CLASS = 16384
HIGH_PRIORITY_CLASS = 128
NORMAL_PRIORITY_CLASS = 32
REALTIME_PRIORITY_CLASS = 256
IDLE_PRIORITY_CLASS = 64
# IO Priority constants (Windows)
IOPRIO_HIGH = 3
IOPRIO_NORMAL = 2
IOPRIO_LOW = 1
# IO Priority constants (Unix/Linux)
IOPRIO_CLASS_IDLE = 3
IOPRIO_CLASS_BE = 2 # Best Effort
IOPRIO_CLASS_RT = 1 # Real TimePRIORITIES = {
"process": {
"verylow": ..., # Platform-specific idle priority
"low": ..., # Below normal priority
"normal": ..., # Standard priority
"high": ..., # Above normal priority
"rt": ... # Real-time priority
},
"io": {
"low": ..., # Background IO priority
"normal": ..., # Standard IO priority
"high": ... # High-priority IO
}
}Custom exception hierarchy for specialized error handling and output preservation.
class TimeoutExpired(BaseException):
"""
Command timeout exception with output preservation.
Compatible backport of subprocess.TimeoutExpired for Python <= 3.3.
Preserves partial command output when timeout occurs.
"""
def __init__(self, cmd, timeout, output=None, stderr=None):
"""
Initialize timeout exception.
Args:
cmd: Command that timed out
timeout: Timeout value in seconds
output: Partial stdout output
stderr: Partial stderr output
"""
@property
def stdout(self):
"""Alias for output property for compatibility."""
class InterruptGetOutput(BaseException):
"""
Base exception for capturing output during interruptions.
Preserves command output when execution is interrupted by various
conditions (timeouts, stop conditions, keyboard interrupts).
"""
def __init__(self, output):
"""
Initialize with preserved output.
Args:
output: Partial command output to preserve
"""
@property
def output(self):
"""Access preserved output from interrupted command."""
class KbdInterruptGetOutput(InterruptGetOutput):
"""
Keyboard interrupt with output preservation.
Raised when KeyboardInterrupt (Ctrl+C) occurs during command execution.
Preserves any output captured before interruption.
"""
class StopOnInterrupt(InterruptGetOutput):
"""
Stop condition interrupt with output preservation.
Raised when custom stop_on function returns True during execution.
Preserves any output captured before stop condition triggered.
"""Version and metadata information for the command_runner package.
# Package metadata
__version__ = "1.7.4"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2015-2025 Orsiris de Jong for NetInvent"
__licence__ = "BSD 3 Clause"
__build__ = "2025052301"
__compat__ = "python2.7+"
# Platform detection
os_name = os.name # 'nt' for Windows, 'posix' for Unix/Linux
# Logger instance
logger = getLogger(__intname__)Command Runner includes compatibility shims for older Python versions:
# Python 2.7 compatibility imports
try:
import queue
except ImportError:
import Queue as queue
try:
from typing import Union, Optional, List, Tuple, Any, Callable
except ImportError:
pass # Type hints not available in Python 2.7
# Python <= 3.3 compatibility
try:
TimeoutExpired = subprocess.TimeoutExpired
except AttributeError:
# Custom TimeoutExpired class for older Python versions
class TimeoutExpired(BaseException): ...Graceful handling of optional dependencies:
# Optional psutil import with fallback
try:
import psutil
HAS_PSUTIL = True
except ImportError:
HAS_PSUTIL = False
# Fallback priority constants defined
# Optional signal module handling
try:
import signal
HAS_SIGNAL = True
except ImportError:
HAS_SIGNAL = Falsefrom command_runner import command_runner, to_encoding
# Windows PowerShell with Unicode output
exit_code, raw_output = command_runner(
'powershell -Command "Get-Process"',
encoding=False # Get raw bytes
)
converted = to_encoding(raw_output, 'unicode_escape', 'backslashreplace')
# Linux with mixed encoding
exit_code, output = command_runner(
'ls -la /path/with/mixed/encoding',
encoding='utf-8',
errors='replace' # Replace invalid characters
)from command_runner import command_runner_threaded
import queue
import threading
def process_command_output():
"""Example of threaded command with live output processing."""
output_queue = queue.Queue()
# Start command in background
future = command_runner_threaded(
'long_running_command',
stdout=output_queue,
method='poller'
)
# Process output as it arrives
while not future.done():
try:
line = output_queue.get(timeout=0.1)
if line is None:
break
process_line(line)
except queue.Empty:
continue
# Get final result
exit_code, output = future.result()
return exit_code, outputfrom command_runner import command_runner, TimeoutExpired, KbdInterruptGetOutput
try:
exit_code, output = command_runner('long_command', timeout=30)
except TimeoutExpired as e:
print(f"Command timed out after {e.timeout} seconds")
print(f"Partial output: {e.output}")
except KbdInterruptGetOutput as e:
print("Command interrupted by user")
print(f"Partial output: {e.output}")Install with Tessl CLI
npx tessl i tessl/pypi-command-runner