Source code for shinobi.results
"""Execution results.
`BackendRun` is the raw, schema-agnostic outcome of a backend actually
running a command: exit status and captured console output, nothing more.
Backends return this; they never see a cab's output schema.
`StepResult` is the schema-aware result the dispatch layer builds on top:
it carries the validated `outputs` and effective `inputs` models plus the
same console output, and is what a step call (cab or recipe) returns.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from pydantic import BaseModel
[docs]
@dataclass
class BackendRun:
"""What a backend returns after running a command -- just the raw run
outcome. Wrangling stdout/stderr into structured outputs and filling
an `outputs_model` is the dispatch layer's job, not the backend's.
"""
returncode: int
stdout: str = ""
stderr: str = ""
@property
def success(self) -> bool:
"""Whether the run exited with return code 0."""
return self.returncode == 0
[docs]
@dataclass
class StepResult:
"""The outcome of running a step (a Cab or a Recipe).
`outputs` is a validated instance of the step's `outputs_model`;
`inputs` is a validated instance of the *effective* (post-override)
inputs the step actually ran with. For a Recipe these aggregate from
its sub-steps.
"""
name: str
returncode: int
outputs: BaseModel
inputs: BaseModel
stdout: str = ""
stderr: str = ""
# True when this result was synthesized from `shinobi.cache` (the step
# itself never actually ran) rather than produced by a real backend run.
cached: bool = False
@property
def success(self) -> bool:
"""Whether the step exited with return code 0."""
return self.returncode == 0
def __getattr__(self, name: str) -> Any:
"""Read through to `outputs` for convenience (`result.<output_field>`).
Args:
name: Attribute name, looked up on `self.outputs` if not a
dataclass field.
Returns:
The corresponding attribute of `self.outputs`.
Raises:
AttributeError: If `name` isn't found on `self.outputs` either.
"""
# convenience: result.<output_field> reads through to outputs
try:
return getattr(self.__dict__["outputs"], name)
except (KeyError, AttributeError):
raise AttributeError(
f"StepResult for '{self.__dict__.get('name')}' has no attribute '{name}'"
) from None