prole/knoe/ops/process.py
chrisfu 4ee2b259c9 Checkpoint: rename installer to knoe + harden db build context
- Add build-context helper to copy Docker context safely (ignore runtime data, keep symlinks)

- Update UI and core actions to use ~/.prole/build and shared copy helper

- Add/adjust tests and scripts; introduce knoe ops helpers and update manifests

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-22 01:45:21 -07:00

94 lines
2.4 KiB
Python

from __future__ import annotations
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Mapping, Sequence
@dataclass(slots=True)
class CommandFailed(RuntimeError):
cmd: Sequence[str]
returncode: int
stdout: str
stderr: str
def __str__(self) -> str: # pragma: no cover
cmd_str = " ".join(self.cmd)
return f"Command failed ({self.returncode}): {cmd_str}"
def run(
cmd: Sequence[str],
*,
cwd: Path | None = None,
env: Mapping[str, str] | None = None,
input_text: str | None = None,
timeout: float | None = None,
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
list(cmd),
cwd=str(cwd) if cwd is not None else None,
env=dict(env) if env is not None else None,
input=input_text,
text=True,
capture_output=True,
timeout=timeout,
)
def check(
cmd: Sequence[str],
*,
cwd: Path | None = None,
env: Mapping[str, str] | None = None,
input_text: str | None = None,
timeout: float | None = None,
) -> subprocess.CompletedProcess[str]:
proc = run(cmd, cwd=cwd, env=env, input_text=input_text, timeout=timeout)
if proc.returncode != 0:
raise CommandFailed(cmd=cmd, returncode=proc.returncode, stdout=proc.stdout, stderr=proc.stderr)
return proc
def run_streaming(
cmd: Sequence[str],
*,
cwd: Path | None = None,
env: Mapping[str, str] | None = None,
stdin_text: str | None = None,
on_line: Callable[[str], None] | None = None,
) -> int:
"""Run a subprocess and stream stdout/stderr lines to `on_line`.
Returns the process return code.
"""
p = subprocess.Popen(
list(cmd),
cwd=str(cwd) if cwd is not None else None,
env=dict(env) if env is not None else None,
stdin=subprocess.PIPE if stdin_text is not None else None,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
try:
if stdin_text is not None and p.stdin is not None:
p.stdin.write(stdin_text)
p.stdin.close()
if p.stdout is not None:
for line in p.stdout:
if on_line is not None:
on_line(line.rstrip("\n"))
return p.wait()
finally:
try:
if p.stdout is not None:
p.stdout.close()
except Exception:
pass