Source code for httk.workflow.backends

"""Runner-backend contracts used by the workflow manager."""

from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Protocol

from .errors import FormatError
from .models import JobDefinition, Marker

if TYPE_CHECKING:
    from .workspace import Workspace

__all__ = [
    "AttemptLaunch",
    "OutcomeCommit",
    "PathRunnerBackend",
    "RunnerBackend",
]


@dataclass(frozen=True)
[docs] class AttemptLaunch: """Paths and context needed to construct an attempt command. ``runner`` is the executable the manager resolved for this attempt. A payload runner is the file inside ``payload``; a shared runner is the verified copy the manager staged below ``control``, never the original. """
[docs] job: JobDefinition
[docs] marker: Marker
[docs] payload: Path
[docs] workdir: Path
[docs] control: Path
[docs] context_path: Path
[docs] context: Mapping[str, Any]
[docs] runner: Path | None = None
@property
[docs] def runner_command(self) -> Path: """Return the executable to run, defaulting to the payload runner.""" if self.runner is not None: return self.runner return self.payload.joinpath(*self.job.runner_path.parts)
@dataclass(frozen=True)
[docs] class OutcomeCommit: """A published outcome being committed by the authoritative manager."""
[docs] job: JobDefinition
[docs] marker: Marker
[docs] payload: Path
[docs] outcome_path: Path
[docs] outcome: Mapping[str, Any]
[docs] class RunnerBackend(Protocol): """Execution behavior selected by ``runner.backend`` in ``job.json``."""
[docs] name: str
[docs] def validate(self, job: JobDefinition, payload: Path) -> None: """Validate backend-specific immutable payload requirements."""
[docs] def command(self, launch: AttemptLaunch) -> Sequence[str]: """Return the command argument vector for one attempt.""" raise NotImplementedError
[docs] def commit_outcome(self, commit: OutcomeCommit) -> None: """Complete backend-specific idempotent work before the marker advances."""
[docs] def reconcile(self, workspace: "Workspace") -> None: """Repair backend-specific derived views; never alter authoritative state."""
[docs] def marker_changed(self, workspace: "Workspace", marker: Marker) -> None: """Refresh derived views after an authoritative marker transition."""
[docs] class PathRunnerBackend: """The normal backend which directly executes ``runner.path``."""
[docs] name = "path"
[docs] def validate(self, job: JobDefinition, payload: Path) -> None: if job.runner_source != "payload": # A shared runner lives outside the immutable payload, so it is # resolved, staged, and digest-verified per attempt instead. return runner = payload.joinpath(*job.runner_path.parts) if not runner.is_file(): raise FormatError(f"runner does not exist or is not a regular file: {job.runner_path}")
[docs] def command(self, launch: AttemptLaunch) -> Sequence[str]: return [str(launch.runner_command), *launch.job.runner_arguments]
[docs] def commit_outcome(self, commit: OutcomeCommit) -> None: return
[docs] def reconcile(self, workspace: "Workspace") -> None: return
[docs] def marker_changed(self, workspace: "Workspace", marker: Marker) -> None: return