Source code for contree_sdk.session.records
"""Durable operation records, independent of live handles and input streams."""
from __future__ import annotations
from copy import deepcopy
from dataclasses import asdict, dataclass, field
from typing import Any, cast
from contree_sdk.files import StagedFile
from contree_sdk.store import JSONValue
[docs]
@dataclass(frozen=True, kw_only=True)
class OperationRecord:
"""A registered operation and its first atomically completed outcome.
request holds the effective command, excluding source streams. A non-null
response marks completion, including failed, cancelled, and discarded
results. history_id is set only when that completion retained an image.
"""
uuid: str
session_id: str
image_uuid: str
parent_id: str
branch: str
title: str
disposable: bool
request: dict[str, JSONValue]
endpoint: str = ""
created_at: float = 0.0
node_id: str = ""
session_cwd: str | None = None
session_env: dict[str, str] = field(default_factory=dict)
files: tuple[str, ...] = ()
attachments: tuple[StagedFile, ...] = ()
response: dict[str, JSONValue] | None = None
history_id: str | None = None
def __post_init__(self) -> None:
if not self.uuid or not self.session_id or not self.image_uuid or not self.branch:
raise ValueError("registered operations require UUID, session, image, and branch")
if not self.parent_id:
raise ValueError("registered operations require a source history entry")
if not isinstance(self.request, dict):
raise TypeError("operation request must be a JSON object")
if self.response is not None and not isinstance(self.response, dict):
raise TypeError("operation response must be a JSON object")
if self.history_id is not None and (not self.history_id or self.response is None):
raise ValueError("completed history requires a response and a nonempty entry ID")
object.__setattr__(self, "request", deepcopy(self.request))
object.__setattr__(self, "response", deepcopy(self.response))
object.__setattr__(self, "session_env", dict(self.session_env))
object.__setattr__(self, "files", tuple(self.files))
object.__setattr__(self, "attachments", tuple(self.attachments))
@property
def pending(self) -> bool:
return self.response is None
[docs]
def to_dict(self) -> dict[str, JSONValue]:
data = asdict(self)
data["files"] = list(self.files)
data["attachments"] = [asdict(item) for item in self.attachments]
return data
[docs]
@classmethod
def from_dict(cls, payload: dict[str, JSONValue]) -> OperationRecord:
data = cast("dict[str, Any]", dict(payload))
data["attachments"] = tuple(StagedFile(**item) for item in data.get("attachments", ()))
return cls(**data)