CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-command-runner

Platform agnostic command and shell execution tool with timeout handling, live output capture, and UAC/sudo privilege elevation

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Helper functions, constants, and utilities that support the core command_runner functionality. Includes encoding conversion, threading support, and platform-specific constants.

Capabilities

Encoding and Output Conversion

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
    """

Threading Support

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
    """

Deferred Execution

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)
    """

Constants and Enumerations

Subprocess Constants

# 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 Time

Priority Mapping Dictionary

PRIORITIES = {
    "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
    }
}

Exception Classes

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.
    """

Module-Level Variables

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__)

Platform Compatibility Utilities

Python Version Compatibility

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): ...

Dependency Detection

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 = False

Usage Patterns

Custom Encoding Scenarios

from 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
)

Threading Integration

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, output

Error Handling with Preserved Output

from 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

docs

core-execution.md

index.md

privilege-elevation.md

process-management.md

utilities.md

tile.json