chore: import upstream snapshot with attribution
Validate YAML Workflows / Validate YAML Configuration Files (push) Has been cancelled
Validate YAML Workflows / Validate YAML Configuration Files (push) Has been cancelled
This commit is contained in:
Executable
+313
@@ -0,0 +1,313 @@
|
||||
"""Utility tool to manage Python environments via uv."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Mapping, Sequence
|
||||
|
||||
_SAFE_PACKAGE_RE = re.compile(r"^[A-Za-z0-9_.\-+=<>!\[\],@:/]+$")
|
||||
_DEFAULT_TIMEOUT = float(os.getenv("LIB_INSTALL_TIMEOUT", "120"))
|
||||
_OUTPUT_SNIPPET_LIMIT = 240
|
||||
|
||||
|
||||
def _trim_output_preview(stdout: str, stderr: str) -> str | None:
|
||||
"""Return a short preview from stdout or stderr for error messaging."""
|
||||
|
||||
preview_source = stdout.strip() or stderr.strip()
|
||||
if not preview_source:
|
||||
return None
|
||||
if len(preview_source) <= _OUTPUT_SNIPPET_LIMIT:
|
||||
return preview_source
|
||||
return f"{preview_source[:_OUTPUT_SNIPPET_LIMIT].rstrip()}... [truncated]"
|
||||
|
||||
|
||||
def _build_timeout_message(step: str | None, timeout_value: float, stdout: str, stderr: str) -> str:
|
||||
"""Create a descriptive timeout error message with optional output preview."""
|
||||
|
||||
label = "uv command"
|
||||
if step:
|
||||
label = f"{label} ({step})"
|
||||
message = f"{label} timed out after {timeout_value} seconds"
|
||||
preview = _trim_output_preview(stdout, stderr)
|
||||
if preview:
|
||||
return f"{message}. Last output: {preview}"
|
||||
return message
|
||||
|
||||
|
||||
class WorkspaceCommandContext:
|
||||
"""Resolve the workspace root from the injected runtime context."""
|
||||
|
||||
def __init__(self, ctx: Dict[str, Any] | None):
|
||||
if ctx is None:
|
||||
raise ValueError("_context is required for uv tools")
|
||||
self.workspace_root = self._require_workspace(ctx.get("python_workspace_root"))
|
||||
self._raw_ctx = ctx
|
||||
|
||||
@staticmethod
|
||||
def _require_workspace(raw_path: Any) -> Path:
|
||||
if raw_path is None:
|
||||
raise ValueError("python_workspace_root missing from _context")
|
||||
path = Path(raw_path).expanduser().resolve()
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
def resolve_under_workspace(self, relative_path: str | Path) -> Path:
|
||||
candidate = Path(relative_path)
|
||||
absolute = candidate if candidate.is_absolute() else self.workspace_root / candidate
|
||||
absolute = absolute.expanduser().resolve()
|
||||
if self.workspace_root not in absolute.parents and absolute != self.workspace_root:
|
||||
raise ValueError("script path is outside workspace root")
|
||||
return absolute
|
||||
|
||||
|
||||
def _validate_packages(packages: Sequence[str]) -> List[str]:
|
||||
normalized: List[str] = []
|
||||
for pkg in packages:
|
||||
if not isinstance(pkg, str):
|
||||
raise ValueError("package entries must be strings")
|
||||
stripped = pkg.strip()
|
||||
if not stripped:
|
||||
raise ValueError("package names cannot be empty")
|
||||
if not _SAFE_PACKAGE_RE.match(stripped):
|
||||
raise ValueError(f"unsafe characters detected in package spec {pkg}")
|
||||
if stripped.startswith("-"):
|
||||
raise ValueError(f"flags are not allowed in packages list: {pkg}")
|
||||
normalized.append(stripped)
|
||||
if not normalized:
|
||||
raise ValueError("at least one package is required")
|
||||
return normalized
|
||||
|
||||
|
||||
def _coerce_timeout_seconds(timeout_seconds: Any) -> float | None:
|
||||
if timeout_seconds is None:
|
||||
return None
|
||||
if isinstance(timeout_seconds, bool):
|
||||
raise ValueError("timeout_seconds must be a number")
|
||||
if isinstance(timeout_seconds, (int, float)):
|
||||
value = float(timeout_seconds)
|
||||
elif isinstance(timeout_seconds, str):
|
||||
raw = timeout_seconds.strip()
|
||||
if not raw:
|
||||
raise ValueError("timeout_seconds cannot be empty")
|
||||
try:
|
||||
if re.fullmatch(r"[+-]?\d+", raw):
|
||||
value = float(int(raw))
|
||||
else:
|
||||
value = float(raw)
|
||||
except ValueError as exc:
|
||||
raise ValueError("timeout_seconds must be a number") from exc
|
||||
else:
|
||||
raise ValueError("timeout_seconds must be a number")
|
||||
|
||||
if value <= 0:
|
||||
raise ValueError("timeout_seconds must be positive")
|
||||
return value
|
||||
|
||||
|
||||
def _validate_flag_args(args: Sequence[str] | None) -> List[str]:
|
||||
normalized: List[str] = []
|
||||
if not args:
|
||||
return normalized
|
||||
for arg in args:
|
||||
if not isinstance(arg, str):
|
||||
raise ValueError("extra args must be strings")
|
||||
stripped = arg.strip()
|
||||
if not stripped:
|
||||
raise ValueError("extra args cannot be empty")
|
||||
if not stripped.startswith("-"):
|
||||
raise ValueError(f"extra args must be flags, got {arg}")
|
||||
normalized.append(stripped)
|
||||
return normalized
|
||||
|
||||
|
||||
def _validate_args(args: Sequence[str] | None) -> List[str]:
|
||||
normalized: List[str] = []
|
||||
if not args:
|
||||
return normalized
|
||||
for arg in args:
|
||||
if not isinstance(arg, str):
|
||||
raise ValueError("args entries must be strings")
|
||||
stripped = arg.strip()
|
||||
if not stripped:
|
||||
raise ValueError("args entries cannot be empty")
|
||||
normalized.append(stripped)
|
||||
return normalized
|
||||
|
||||
|
||||
def _validate_env(env: Mapping[str, str] | None) -> Dict[str, str]:
|
||||
if env is None:
|
||||
return {}
|
||||
result: Dict[str, str] = {}
|
||||
for key, value in env.items():
|
||||
if not isinstance(key, str) or not key:
|
||||
raise ValueError("environment variable keys must be non-empty strings")
|
||||
if not isinstance(value, str):
|
||||
raise ValueError("environment variable values must be strings")
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def _run_uv_command(
|
||||
cmd: List[str],
|
||||
workspace_root: Path,
|
||||
*,
|
||||
step: str | None = None,
|
||||
env: Dict[str, str] | None = None,
|
||||
timeout: float | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
timeout_value = _DEFAULT_TIMEOUT if timeout is None else timeout
|
||||
env_vars = None if env is None else {**os.environ, **env}
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
cmd,
|
||||
cwd=str(workspace_root),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_value,
|
||||
check=False,
|
||||
env=env_vars,
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
raise RuntimeError("uv command not found in PATH") from exc
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
stdout_text = exc.stdout
|
||||
if stdout_text is None:
|
||||
stdout_text = getattr(exc, "output", "") or ""
|
||||
stderr_text = exc.stderr or ""
|
||||
message = _build_timeout_message(step, timeout_value, stdout_text, stderr_text)
|
||||
return {
|
||||
"command": cmd,
|
||||
"stdout": stdout_text,
|
||||
"stderr": stderr_text,
|
||||
"returncode": None,
|
||||
"step": step,
|
||||
"timed_out": True,
|
||||
"timeout": timeout_value,
|
||||
"error": message,
|
||||
}
|
||||
|
||||
return {
|
||||
"command": cmd,
|
||||
"stdout": completed.stdout or "",
|
||||
"stderr": completed.stderr or "",
|
||||
"returncode": completed.returncode,
|
||||
# "cwd": str(workspace_root),
|
||||
"step": step,
|
||||
}
|
||||
|
||||
|
||||
def install_python_packages(
|
||||
packages: Sequence[str],
|
||||
*,
|
||||
upgrade: bool = False,
|
||||
# extra_args: Sequence[str] | None = None,
|
||||
_context: Dict[str, Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Install Python packages inside the workspace using uv add."""
|
||||
|
||||
ctx = WorkspaceCommandContext(_context)
|
||||
safe_packages = _validate_packages(packages)
|
||||
cmd: List[str] = ["uv", "add"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
|
||||
# if extra_args:
|
||||
# flags = _validate_flag_args(extra_args)
|
||||
# cmd.extend(flags)
|
||||
|
||||
cmd.extend(safe_packages)
|
||||
result = _run_uv_command(cmd, ctx.workspace_root, step="uv add")
|
||||
# result["workspace_root"] = str(ctx.workspace_root)
|
||||
return result
|
||||
|
||||
|
||||
def init_python_env(
|
||||
*,
|
||||
# recreate: bool = False,
|
||||
python_version: str | None = None,
|
||||
# lock_args: Sequence[str] | None = None,
|
||||
# venv_args: Sequence[str] | None = None,
|
||||
_context: Dict[str, Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Run uv lock and uv venv inside the workspace."""
|
||||
|
||||
ctx = WorkspaceCommandContext(_context)
|
||||
steps: List[Dict[str, Any]] = []
|
||||
|
||||
lock_cmd: List[str] = ["uv", "lock"]
|
||||
# lock_cmd.extend(_validate_flag_args(lock_args))
|
||||
lock_result = _run_uv_command(lock_cmd, ctx.workspace_root, step="uv lock")
|
||||
steps.append(lock_result)
|
||||
if lock_result["returncode"] != 0:
|
||||
return {
|
||||
"workspace_root": str(ctx.workspace_root),
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
venv_cmd: List[str] = ["uv", "venv"]
|
||||
# if recreate:
|
||||
# venv_cmd.append("--recreate")
|
||||
# venv_cmd.extend(_validate_flag_args(venv_args))
|
||||
if python_version is not None:
|
||||
python_spec = python_version.strip()
|
||||
if not python_spec:
|
||||
raise ValueError("python argument cannot be empty")
|
||||
venv_cmd.extend(["--python", python_spec])
|
||||
|
||||
venv_result = _run_uv_command(venv_cmd, ctx.workspace_root, step="uv venv")
|
||||
steps.append(venv_result)
|
||||
|
||||
init_cmd: List[str] = ["uv", "init", "--bare", "--no-workspace"]
|
||||
init_result = _run_uv_command(init_cmd, ctx.workspace_root, step="uv init")
|
||||
steps.append(init_result)
|
||||
|
||||
return {
|
||||
"workspace_root": str(ctx.workspace_root),
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
|
||||
def uv_run(
|
||||
*,
|
||||
module: str | None = None,
|
||||
script: str | None = None,
|
||||
args: Sequence[str] | None = None,
|
||||
env: Mapping[str, str] | None = None,
|
||||
timeout_seconds: float | None = None,
|
||||
_context: Dict[str, Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute uv run for a module or script inside the workspace root."""
|
||||
|
||||
ctx = WorkspaceCommandContext(_context)
|
||||
timeout_seconds = _coerce_timeout_seconds(timeout_seconds)
|
||||
|
||||
has_module = module is not None
|
||||
has_script = script is not None
|
||||
if has_module == has_script:
|
||||
raise ValueError("Provide exactly one of module or script")
|
||||
|
||||
cmd: List[str] = ["uv", "run"]
|
||||
if has_module:
|
||||
module_name = module.strip()
|
||||
if not module_name:
|
||||
raise ValueError("module cannot be empty")
|
||||
cmd.extend(["python", "-m", module_name])
|
||||
else:
|
||||
script_value = script.strip() if isinstance(script, str) else script
|
||||
if not script_value:
|
||||
raise ValueError("script cannot be empty")
|
||||
script_path = ctx.resolve_under_workspace(script_value)
|
||||
cmd.append(str(script_path))
|
||||
|
||||
cmd.extend(_validate_args(args))
|
||||
env_overrides = _validate_env(env)
|
||||
result = _run_uv_command(
|
||||
cmd,
|
||||
ctx.workspace_root,
|
||||
step="uv run",
|
||||
env=env_overrides,
|
||||
timeout=timeout_seconds,
|
||||
)
|
||||
result["workspace_root"] = str(ctx.workspace_root)
|
||||
return result
|
||||
Reference in New Issue
Block a user