Process
provide.foundation.process
¶
TODO: Add module docstring.
Classes¶
CompletedProcess
¶
Result of a completed process.
Note
The env field only stores caller-provided environment variable overrides,
not the full subprocess environment. This prevents credential leakage when
CompletedProcess objects are logged or stored.
ManagedProcess
¶
ManagedProcess(
command: list[str],
*,
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
capture_output: bool = True,
text_mode: bool = False,
bufsize: int = 0,
stderr_relay: bool = True,
**kwargs: Any
)
A managed subprocess with lifecycle support, monitoring, and graceful shutdown.
This class wraps subprocess.Popen with additional functionality for: - Environment management - Output streaming and monitoring - Health checks and process monitoring - Graceful shutdown with timeouts - Background stderr relaying
Initialize a ManagedProcess.
Source code in provide/foundation/process/lifecycle/managed.py
Attributes¶
Functions¶
__enter__
¶
__exit__
¶
cleanup
¶
Clean up process resources.
Source code in provide/foundation/process/lifecycle/managed.py
is_running
¶
launch
¶
Launch the managed process.
Raises:
| Type | Description |
|---|---|
ProcessError
|
If the process fails to launch |
StateError
|
If the process is already started |
Source code in provide/foundation/process/lifecycle/managed.py
read_char_async
async
¶
Read a single character from stdout asynchronously.
Source code in provide/foundation/process/lifecycle/managed.py
read_line_async
async
¶
Read a line from stdout asynchronously with timeout.
Source code in provide/foundation/process/lifecycle/managed.py
terminate_gracefully
¶
Terminate the process gracefully with a timeout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
Maximum time to wait for graceful termination |
DEFAULT_PROCESS_TERMINATE_TIMEOUT
|
Returns:
| Type | Description |
|---|---|
bool
|
True if process terminated gracefully, False if force-killed |
Source code in provide/foundation/process/lifecycle/managed.py
ProcessError
¶
ProcessError(
message: str,
*,
command: str | list[str] | None = None,
return_code: int | None = None,
stdout: str | bytes | None = None,
stderr: str | bytes | None = None,
timeout: bool = False,
code: str | None = None,
**extra_context: Any
)
Bases: FoundationError
Error for external process execution failures with output capture.
Initialize ProcessError with command execution details.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable error message |
required |
command
|
str | list[str] | None
|
The command that was executed |
None
|
return_code
|
int | None
|
Process return/exit code |
None
|
stdout
|
str | bytes | None
|
Standard output from the process |
None
|
stderr
|
str | bytes | None
|
Standard error from the process |
None
|
timeout
|
bool
|
Whether the process timed out |
False
|
code
|
str | None
|
Optional error code |
None
|
**extra_context
|
Any
|
Additional context information |
{}
|
Source code in provide/foundation/errors/process.py
Functions¶
Functions¶
async_run
async
¶
async_run(
cmd: list[str] | str,
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
capture_output: bool = True,
check: bool = True,
timeout: float | None = None,
input: bytes | None = None,
shell: bool = False,
**kwargs: Any
) -> CompletedProcess
Run a subprocess command asynchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
list[str] | str
|
Command and arguments as a list |
required |
cwd
|
str | Path | None
|
Working directory for the command |
None
|
env
|
Mapping[str, str] | None
|
Environment variables (if None, uses current environment) |
None
|
capture_output
|
bool
|
Whether to capture stdout/stderr |
True
|
check
|
bool
|
Whether to raise exception on non-zero exit |
True
|
timeout
|
float | None
|
Command timeout in seconds |
None
|
input
|
bytes | None
|
Input to send to the process |
None
|
shell
|
bool
|
Whether to execute via shell |
False
|
**kwargs
|
Any
|
Additional subprocess arguments |
{}
|
Returns:
| Type | Description |
|---|---|
CompletedProcess
|
CompletedProcess with results |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If command type and shell parameter mismatch |
ProcessError
|
If command fails and check=True |
ProcessTimeoutError
|
If timeout is exceeded |
Source code in provide/foundation/process/aio/execution.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | |
async_shell
async
¶
async_shell(
cmd: str,
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
capture_output: bool = True,
check: bool = True,
timeout: float | None = None,
allow_shell_features: bool = DEFAULT_SHELL_ALLOW_FEATURES,
**kwargs: Any
) -> CompletedProcess
Run a shell command asynchronously with safety validation.
WARNING: This function uses shell=True. By default, shell metacharacters are DENIED to prevent command injection. Use allow_shell_features=True only with trusted input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
str
|
Shell command string |
required |
cwd
|
str | Path | None
|
Working directory |
None
|
env
|
Mapping[str, str] | None
|
Environment variables |
None
|
capture_output
|
bool
|
Whether to capture output |
True
|
check
|
bool
|
Whether to raise on non-zero exit |
True
|
timeout
|
float | None
|
Command timeout in seconds |
None
|
allow_shell_features
|
bool
|
Allow shell metacharacters (default: False) |
DEFAULT_SHELL_ALLOW_FEATURES
|
**kwargs
|
Any
|
Additional subprocess arguments |
{}
|
Returns:
| Type | Description |
|---|---|
CompletedProcess
|
CompletedProcess with results |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If cmd is not a string |
ShellFeatureError
|
If shell features used without explicit permission |
Security Note
For maximum security, use async_run() with a list of arguments instead. Only set allow_shell_features=True if you fully trust the command source.
Safe: await async_shell("ls -la", allow_shell_features=False) # OK Unsafe: await async_shell(user_input) # Will raise ShellFeatureError if metacharacters present Risky: await async_shell(user_input, allow_shell_features=True) # DO NOT DO THIS
Source code in provide/foundation/process/aio/shell.py
async_stream
async
¶
async_stream(
cmd: list[str],
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
timeout: float | None = None,
stream_stderr: bool = False,
**kwargs: Any
) -> AsyncIterator[str]
Stream command output line by line asynchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
list[str]
|
Command and arguments as a list |
required |
cwd
|
str | Path | None
|
Working directory for the command |
None
|
env
|
Mapping[str, str] | None
|
Environment variables |
None
|
timeout
|
float | None
|
Command timeout in seconds |
None
|
stream_stderr
|
bool
|
Whether to merge stderr into stdout |
False
|
**kwargs
|
Any
|
Additional subprocess arguments |
{}
|
Yields:
| Type | Description |
|---|---|
AsyncIterator[str]
|
Lines of output from the command |
Raises:
| Type | Description |
|---|---|
ProcessError
|
If command fails |
ProcessTimeoutError
|
If timeout is exceeded |
Source code in provide/foundation/process/aio/streaming.py
exit_error
¶
Exit with error status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Optional error message to log before exiting |
None
|
code
|
int
|
Exit code to use (defaults to EXIT_ERROR) |
EXIT_ERROR
|
Source code in provide/foundation/process/exit.py
exit_interrupted
¶
Exit due to interrupt signal (SIGINT).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Message to log before exiting |
'Process interrupted'
|
Source code in provide/foundation/process/exit.py
exit_success
¶
Exit with success status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Optional message to log before exiting |
None
|
Source code in provide/foundation/process/exit.py
get_name
¶
Get process name (PR_GET_NAME).
Returns:
| Type | Description |
|---|---|
str | None
|
Process name, or None if prctl is not available |
Raises:
| Type | Description |
|---|---|
PlatformError
|
If not on Linux or python-prctl not installed |
Example
from provide.foundation.process import get_name get_name() 'worker-1'
Source code in provide/foundation/process/prctl.py
get_process_title
¶
Get the current process title.
Automatically returns None in test mode (via @skip_in_test_mode decorator) to prevent test interference.
Returns:
| Type | Description |
|---|---|
str | None
|
The current process title, or None if setproctitle is not available |
str | None
|
or running in test mode |
Example
from provide.foundation.process import get_process_title, set_process_title set_process_title("my-process") True get_process_title() 'my-process'
Source code in provide/foundation/process/title.py
has_prctl
¶
Check if prctl is available.
Returns:
| Type | Description |
|---|---|
bool
|
True if running on Linux and python-prctl is installed, False otherwise |
Example
from provide.foundation.process import has_prctl if has_prctl(): ... # Use prctl features ... pass
Source code in provide/foundation/process/prctl.py
has_setproctitle
¶
Check if setproctitle is available.
Returns:
| Type | Description |
|---|---|
bool
|
True if setproctitle is available, False otherwise |
Example
from provide.foundation.process import has_setproctitle if has_setproctitle(): ... # Use process title features ... pass
Source code in provide/foundation/process/title.py
is_linux
¶
Check if running on Linux.
Returns:
| Type | Description |
|---|---|
bool
|
True if running on Linux, False otherwise |
Example
from provide.foundation.process import is_linux is_linux() True
Source code in provide/foundation/process/prctl.py
run
¶
run(
cmd: list[str] | str,
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
capture_output: bool = True,
check: bool = True,
timeout: float | None = None,
text: bool = True,
input: str | bytes | None = None,
shell: bool = False,
**kwargs: Any
) -> CompletedProcess
Run a subprocess command with consistent error handling and logging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
list[str] | str
|
Command and arguments as a list |
required |
cwd
|
str | Path | None
|
Working directory for the command |
None
|
env
|
Mapping[str, str] | None
|
Environment variables (if None, uses current environment) |
None
|
capture_output
|
bool
|
Whether to capture stdout/stderr |
True
|
check
|
bool
|
Whether to raise exception on non-zero exit |
True
|
timeout
|
float | None
|
Command timeout in seconds |
None
|
text
|
bool
|
Whether to decode output as text |
True
|
input
|
str | bytes | None
|
Input to send to the process |
None
|
shell
|
bool
|
Whether to run command through shell |
False
|
**kwargs
|
Any
|
Additional arguments passed to subprocess.run |
{}
|
Returns:
| Type | Description |
|---|---|
CompletedProcess
|
CompletedProcess with results |
Raises:
| Type | Description |
|---|---|
ProcessError
|
If command fails and check=True |
ProcessTimeoutError
|
If timeout is exceeded |
Source code in provide/foundation/process/sync/execution.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
run_simple
¶
Simple wrapper for run that returns stdout as a string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
list[str]
|
Command and arguments as a list |
required |
cwd
|
str | Path | None
|
Working directory for the command |
None
|
**kwargs
|
Any
|
Additional arguments passed to run |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
Stdout as a stripped string |
Raises:
| Type | Description |
|---|---|
ProcessError
|
If command fails |
Source code in provide/foundation/process/sync/execution.py
set_death_signal
¶
Set signal to be sent to process when parent dies (PR_SET_PDEATHSIG).
This is useful for ensuring child processes are cleaned up when the parent terminates unexpectedly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signal
|
int
|
Signal number to send (e.g., signal.SIGTERM, signal.SIGKILL) |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if successful, False otherwise |
Raises:
| Type | Description |
|---|---|
PlatformError
|
If not on Linux or python-prctl not installed |
Example
import signal from provide.foundation.process import set_death_signal set_death_signal(signal.SIGTERM) # Send SIGTERM when parent dies True
Source code in provide/foundation/process/prctl.py
set_dumpable
¶
Set whether process can produce core dumps (PR_SET_DUMPABLE).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dumpable
|
bool
|
True to allow core dumps, False to disable |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if successful, False otherwise |
Raises:
| Type | Description |
|---|---|
PlatformError
|
If not on Linux or python-prctl not installed |
Example
from provide.foundation.process import set_dumpable set_dumpable(False) # Disable core dumps for security True
Source code in provide/foundation/process/prctl.py
set_name
¶
Set process name (PR_SET_NAME).
Note: This is different from setproctitle. PR_SET_NAME sets the comm value in /proc/[pid]/comm (limited to 16 bytes including null terminator).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Process name (max 15 characters) |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if successful, False otherwise |
Raises:
| Type | Description |
|---|---|
PlatformError
|
If not on Linux or python-prctl not installed |
Example
from provide.foundation.process import set_name set_name("worker-1") True
Source code in provide/foundation/process/prctl.py
set_no_new_privs
¶
Set no_new_privs flag (PR_SET_NO_NEW_PRIVS).
When enabled, execve() will not grant privileges to do anything that could not have been done without the execve() call. This is a security feature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
True to enable no_new_privs, False to attempt disable (usually fails) |
True
|
Returns:
| Type | Description |
|---|---|
bool
|
True if successful, False otherwise |
Raises:
| Type | Description |
|---|---|
PlatformError
|
If not on Linux or python-prctl not installed |
Example
from provide.foundation.process import set_no_new_privs set_no_new_privs(True) # Prevent privilege escalation True
Source code in provide/foundation/process/prctl.py
set_process_title
¶
Set the process title visible in system monitoring tools.
The process title is what appears in ps, top, htop, and other system monitoring tools. This is useful for identifying processes, especially in multi-process applications or long-running services.
Automatically disabled in test mode (via @skip_in_test_mode decorator) to prevent interference with test isolation and parallel test execution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
The title to set for the current process |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the title was set successfully (or skipped in test mode), |
bool
|
False if setproctitle is not available |
Example
from provide.foundation.process import set_process_title set_process_title("my-worker-process") True
Process will now show as "my-worker-process" in ps/top¶
Source code in provide/foundation/process/title.py
set_process_title_from_argv
¶
Set process title from argv, preserving the invoked command name.
Extracts the command name from sys.argv[0] (including symlinks) and formats it with the remaining arguments to create a clean process title.
This handles symlinks correctly - if you have a symlink 'whatever' pointing to 'pyvider', and run 'whatever run --config foo.yml', the process title will be 'whatever run --config foo.yml'.
Automatically disabled in test mode (via @skip_in_test_mode decorator) to prevent interference with test isolation and parallel test execution.
Returns:
| Type | Description |
|---|---|
bool
|
True if the title was set successfully (or skipped in test mode), |
bool
|
False if setproctitle is not available |
Example
If invoked as: pyvider run --config foo.yml¶
from provide.foundation.process import set_process_title_from_argv set_process_title_from_argv() True
Process will show as "pyvider run --config foo.yml" in ps/top¶
If invoked via symlink: whatever run¶
(where whatever -> pyvider)¶
set_process_title_from_argv() True
Process will show as "whatever run" in ps/top¶
Source code in provide/foundation/process/title.py
stream
¶
stream(
cmd: list[str],
cwd: str | Path | None = None,
env: Mapping[str, str] | None = None,
timeout: float | None = None,
stream_stderr: bool = False,
**kwargs: Any
) -> Iterator[str]
Stream command output line by line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmd
|
list[str]
|
Command and arguments as a list |
required |
cwd
|
str | Path | None
|
Working directory for the command |
None
|
env
|
Mapping[str, str] | None
|
Environment variables |
None
|
timeout
|
float | None
|
Command timeout in seconds |
None
|
stream_stderr
|
bool
|
Whether to stream stderr (merged with stdout) |
False
|
**kwargs
|
Any
|
Additional arguments passed to subprocess.Popen |
{}
|
Yields:
| Type | Description |
|---|---|
str
|
Lines of output from the command |
Raises:
| Type | Description |
|---|---|
ProcessError
|
If command fails |
ProcessTimeoutError
|
If timeout is exceeded |
Source code in provide/foundation/process/sync/streaming.py
wait_for_process_output
async
¶
wait_for_process_output(
process: ManagedProcess,
expected_parts: list[str],
timeout: float = DEFAULT_PROCESS_WAIT_TIMEOUT,
buffer_size: int = 1024,
) -> str
Wait for specific output pattern from a managed process.
This utility reads from a process stdout until a specific pattern (e.g., handshake string with multiple pipe separators) appears.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
process
|
ManagedProcess
|
The managed process to read from |
required |
expected_parts
|
list[str]
|
List of expected parts/separators in the output |
required |
timeout
|
float
|
Maximum time to wait for the pattern |
DEFAULT_PROCESS_WAIT_TIMEOUT
|
buffer_size
|
int
|
Size of read buffer |
1024
|
Returns:
| Type | Description |
|---|---|
str
|
The complete output buffer containing the expected pattern |
Raises:
| Type | Description |
|---|---|
ProcessError
|
If process exits unexpectedly |
TimeoutError
|
If pattern is not found within timeout |