Source code for contree_sdk.session.base

from __future__ import annotations

from io import TextIOBase
from types import EllipsisType
from typing import IO, Any, Literal, cast
from uuid import uuid4

from contree_client.models import (
    ClosableStreamRepr,
    EventDataStream,
    FileSpec,
    InstanceResult,
    OperationResponse,
    OperationStatus,
    StreamRepr,
)

from contree_sdk.exceptions import FailedOperationError
from contree_sdk.files import RunFiles  # noqa: F401 - public re-export for existing module users
from contree_sdk.utils.models.file import UploadedFile, UploadFileSpec


def or_none(value: Any) -> Any | None:
    return None if value is Ellipsis else value


def require_str(value: str | EllipsisType | None, message: str) -> str:
    if value is None or isinstance(value, EllipsisType):
        raise ValueError(message)
    return value


def new_session_id() -> str:
    return uuid4().hex


def validate_command(command: str | None, shell: str | None) -> str:
    if command is not None and shell is not None:
        raise ValueError("command and shell are mutually exclusive")
    resolved = shell if shell is not None else command
    if resolved is None:
        raise ValueError("either command or shell must be provided")
    return resolved


[docs] def instance_result(op: OperationResponse) -> InstanceResult: """Get the operation's `InstanceResult`. Returns: The `InstanceResult` from `op.metadata.result`. Raises: InterruptedError: The operation was cancelled. FailedOperationError: The operation itself failed with no result at all (a nonzero exit code inside a successful operation is not an error). """ if op.status == OperationStatus.CANCELLED: raise InterruptedError(f"operation {op.uuid} was cancelled") if op.status != OperationStatus.SUCCESS: raise FailedOperationError(require_str(op.uuid, "operation response missing uuid"), or_none(op.error)) result = or_none(getattr(op.metadata, "result", Ellipsis)) if result is None: raise FailedOperationError(require_str(op.uuid, "operation response missing uuid"), or_none(op.error)) return result
def exit_code_of(result: InstanceResult) -> int | None: state = or_none(result.state) return None if state is None else or_none(state.exit_code) def stream_repr_for_stdin(data: str | bytes, *, close: bool = True) -> ClosableStreamRepr: repr_ = StreamRepr.from_text(data) if isinstance(data, str) else StreamRepr.from_bytes(data) return ClosableStreamRepr(value=repr_.value, encoding=repr_.encoding, close=close) def file_spec_for(uploaded: UploadedFile, file: UploadFileSpec) -> FileSpec: return FileSpec(uuid=uploaded.uuid, uid=file.uid, gid=file.gid, mode=file.mode) def encode_stdin_chunk(data: str | bytes) -> tuple[str, Literal["ascii", "base64"]]: payload = StreamRepr.from_text(data) if isinstance(data, str) else StreamRepr.from_bytes(data) return payload.value, payload.encoding def write_stream_chunk(stream: IO[str] | IO[bytes], data: object) -> None: if not isinstance(data, EventDataStream): raise TypeError("expected an EventDataStream payload") if isinstance(stream, TextIOBase): cast("IO[str]", stream).write(data.as_text()) else: cast("IO[bytes]", stream).write(data.as_bytes())