Skip to content

Runner

runner

Subprocess runner that targets the audited project's venv.

When auditing an external project, tools (ruff, mypy, pytest, etc.) must execute in that project's virtual environment, not axm-audit's own.

find_venv(project_path)

Locate the nearest .venv directory for a project.

Checks project_path first, then walks up the directory tree to support uv monorepo workspaces where the shared .venv lives at the workspace root rather than inside the individual package.

The search is bounded to :data:_MAX_VENV_SEARCH_DEPTH levels to avoid accidentally picking up an unrelated .venv higher in the file system.

Parameters:

Name Type Description Default
project_path Path

Root of the project being audited.

required

Returns:

Type Description
Path | None

The .venv directory if found, or None if no virtual

Path | None

environment exists in the project or any of its ancestors

Path | None

(within the bounded depth).

Source code in packages/axm-audit/src/axm_audit/core/runner.py
Python
def find_venv(project_path: Path) -> Path | None:
    """Locate the nearest ``.venv`` directory for a project.

    Checks ``project_path`` first, then walks up the directory tree to
    support **uv monorepo workspaces** where the shared ``.venv`` lives
    at the workspace root rather than inside the individual package.

    The search is bounded to :data:`_MAX_VENV_SEARCH_DEPTH` levels to
    avoid accidentally picking up an unrelated ``.venv`` higher in the
    file system.

    Args:
        project_path: Root of the project being audited.

    Returns:
        The ``.venv`` directory if found, or ``None`` if no virtual
        environment exists in the project or any of its ancestors
        (within the bounded depth).
    """
    current = project_path.resolve()
    # Walk up at most _MAX_VENV_SEARCH_DEPTH levels
    for directory in itertools.islice(
        (current, *current.parents), _MAX_VENV_SEARCH_DEPTH
    ):
        venv_python = directory / ".venv" / "bin" / "python"
        if venv_python.exists():
            return directory / ".venv"
    return None

run_in_project(cmd, project_path, *, timeout=_DEFAULT_TIMEOUT, with_packages=None, capture_output=False, text=False, check=False)

Run a command in the target project's environment.

Locates the nearest .venv/ — either in project_path itself or in an ancestor directory (for uv monorepo workspace members). Uses uv run --directory to execute the command within the correct environment. Falls back to running the command directly with cwd set when no virtual environment is found.

Parameters:

Name Type Description Default
cmd list[str]

Command and arguments to run.

required
project_path Path

Root of the project being audited.

required
timeout int

Maximum seconds to wait before killing the subprocess. Defaults to 300 (5 minutes).

_DEFAULT_TIMEOUT
with_packages list[str] | None

Optional packages to inject at runtime via uv run --with <pkg>. Only effective when a .venv/ is found (i.e. when uv run is used). Allows audit tools to be available in the target project without requiring them as declared dependencies.

None
capture_output bool

Forwarded to subprocess.run; capture stdout/stderr.

False
text bool

Forwarded to subprocess.run; decode output as text.

False
check bool

Forwarded to subprocess.run; raise on non-zero exit.

False

Returns:

Type Description
CompletedProcess[str]

CompletedProcess result. On timeout, returns a synthetic result

CompletedProcess[str]

with returncode=124 and the timeout message in stderr.

Source code in packages/axm-audit/src/axm_audit/core/runner.py
Python
def run_in_project(  # noqa: PLR0913
    cmd: list[str],
    project_path: Path,
    *,
    timeout: int = _DEFAULT_TIMEOUT,
    with_packages: list[str] | None = None,
    capture_output: bool = False,
    text: bool = False,
    check: bool = False,
) -> subprocess.CompletedProcess[str]:
    """Run a command in the target project's environment.

    Locates the nearest ``.venv/`` — either in ``project_path`` itself
    or in an ancestor directory (for uv monorepo workspace members).
    Uses ``uv run --directory`` to execute the command within the
    correct environment.  Falls back to running the command directly
    with ``cwd`` set when no virtual environment is found.

    Args:
        cmd: Command and arguments to run.
        project_path: Root of the project being audited.
        timeout: Maximum seconds to wait before killing the subprocess.
            Defaults to 300 (5 minutes).
        with_packages: Optional packages to inject at runtime via
            ``uv run --with <pkg>``.  Only effective when a ``.venv/``
            is found (i.e. when ``uv run`` is used).  Allows audit tools
            to be available in the target project without requiring
            them as declared dependencies.
        capture_output: Forwarded to ``subprocess.run``; capture stdout/stderr.
        text: Forwarded to ``subprocess.run``; decode output as text.
        check: Forwarded to ``subprocess.run``; raise on non-zero exit.

    Returns:
        CompletedProcess result.  On timeout, returns a synthetic result
        with ``returncode=124`` and the timeout message in ``stderr``.
    """
    venv = find_venv(project_path)
    cwd: str | None = None

    if venv is not None:
        with_flags: list[str] = []
        for pkg in with_packages or []:
            with_flags.extend(["--with", pkg])
        full_cmd = ["uv", "run", *with_flags, "--directory", str(project_path), *cmd]
    else:
        full_cmd = cmd
        cwd = str(project_path)

    try:
        return subprocess.run(  # noqa: S603
            full_cmd,
            timeout=timeout,
            capture_output=capture_output,
            text=text,
            check=check,
            cwd=cwd,
        )
    except subprocess.TimeoutExpired:
        cmd_str = " ".join(full_cmd)
        logger.warning("Command timed out after %ds: %s", timeout, cmd_str)
        return subprocess.CompletedProcess(
            args=full_cmd,
            returncode=124,
            stdout="",
            stderr=f"Command timed out after {timeout}s: {cmd_str}",
        )