Skip to content

Pytest env

pytest_env

What the target interpreter offers pytest, probed rather than assumed.

audit_test runs the target's pytest. Its structured result comes from a plugin the runner ships (:mod:axm_audit._report_plugin) and injects with -p and PYTHONPATH, so the target needs nothing but pytest itself. Coverage is the only optional capability (pytest-cov): this module asks the target interpreter, once per run, whether pytest and pytest-cov are importable, so that a missing piece becomes a typed, explicit outcome instead of pytest's opaque unrecognized arguments (exit 4).

MIN_PYTEST_VERSION = (7, 0) module-attribute

Oldest pytest whose hooks the reporting plugin relies on.

PLUGIN_DIR = Path(__file__).resolve().parent.parent / '_report_plugin' module-attribute

Directory put on the subprocess PYTHONPATH; it holds the plugin only.

PLUGIN_MODULE = 'axm_audit_report_plugin' module-attribute

Name the reporting plugin is loaded under (-p).

PytestCapabilities dataclass

What the target interpreter was found to provide.

Source code in packages/axm-audit/src/axm_audit/core/pytest_env.py
Python
@dataclass(frozen=True, slots=True)
class PytestCapabilities:
    """What the target interpreter was found to provide."""

    interpreter: str
    pytest_version: str
    coverage_available: bool

    @property
    def coverage_unavailable_reason(self) -> str | None:
        """Why coverage cannot be measured here, or ``None`` when it can."""
        if self.coverage_available:
            return None
        return f"pytest-cov is not installed in {self.interpreter}"
coverage_unavailable_reason property

Why coverage cannot be measured here, or None when it can.

PytestEnvironmentError

Bases: ValueError

The target interpreter cannot run audit_test's pytest session.

interpreter is the interpreter that was asked (when known) and missing names what it lacks: "pytest", "pytest>=X" or "interpreter".

Source code in packages/axm-audit/src/axm_audit/core/pytest_env.py
Python
class PytestEnvironmentError(ValueError):
    """The target interpreter cannot run ``audit_test``'s pytest session.

    ``interpreter`` is the interpreter that was asked (when known) and
    ``missing`` names what it lacks: ``"pytest"``, ``"pytest>=X"`` or
    ``"interpreter"``.
    """

    def __init__(self, message: str, *, interpreter: str, missing: str) -> None:
        super().__init__(message)
        self.interpreter = interpreter
        self.missing = missing

probe_pytest(project_path, *, with_packages=None)

Probe the interpreter audit_test will use for project_path.

The probe goes through :func:run_in_project, hence through exactly the interpreter selection the test run itself will get (declared session runtime, worktree source environment, project .venv, uv overlay).

Raises:

Type Description
PytestEnvironmentError

the interpreter cannot be run, has no pytest, or has a pytest older than :data:MIN_PYTEST_VERSION.

Source code in packages/axm-audit/src/axm_audit/core/pytest_env.py
Python
def probe_pytest(
    project_path: Path,
    *,
    with_packages: list[str] | None = None,
) -> PytestCapabilities:
    """Probe the interpreter ``audit_test`` will use for *project_path*.

    The probe goes through :func:`run_in_project`, hence through exactly the
    interpreter selection the test run itself will get (declared session
    runtime, worktree source environment, project ``.venv``, ``uv`` overlay).

    Raises:
        PytestEnvironmentError: the interpreter cannot be run, has no pytest,
            or has a pytest older than :data:`MIN_PYTEST_VERSION`.
    """
    try:
        result = run_in_project(
            ["python", "-c", _PROBE_SCRIPT],
            project_path,
            timeout=_PROBE_TIMEOUT,
            with_packages=with_packages,
            capture_output=True,
            text=True,
        )
    except (OSError, ValueError) as exc:
        raise PytestEnvironmentError(
            f"cannot run the Python interpreter for {project_path}: {exc}",
            interpreter="unknown",
            missing="interpreter",
        ) from exc

    data = _parse_probe(result.stdout or "")
    if data is None:
        tail = (result.stderr or result.stdout or "").strip()[-600:]
        raise PytestEnvironmentError(
            f"cannot probe the Python interpreter for {project_path} "
            f"(exit {result.returncode}): {tail}",
            interpreter="unknown",
            missing="interpreter",
        )

    interpreter = str(data.get("python", "unknown"))
    version = data.get("pytest")
    if not isinstance(version, str):
        raise PytestEnvironmentError(
            f"pytest is not installed in {interpreter} "
            f"({data.get('pytest_error', 'import failed')}); audit_test runs the "
            "target's own pytest and needs it in that interpreter",
            interpreter=interpreter,
            missing="pytest",
        )
    if _version_tuple(version) < MIN_PYTEST_VERSION:
        needed = ".".join(map(str, MIN_PYTEST_VERSION))
        raise PytestEnvironmentError(
            f"pytest {version} in {interpreter} is too old for audit_test's "
            f"reporting hooks; pytest>={needed} is required",
            interpreter=interpreter,
            missing=f"pytest>={needed}",
        )
    return PytestCapabilities(
        interpreter=interpreter,
        pytest_version=version,
        coverage_available=bool(data.get("pytest_cov")),
    )