Skip to content

monty-types

The shared boundary types exchanged between hosts and the sandbox: values, exceptions, resource limits and host-call payloads. Host-side code depends on this crate rather than on the interpreter.

args

pub mod args;

Projection of typed argument structs into the CallArgs host callbacks consume. The #[derive(ToArgs)] macro in monty-macros emits implementations via crate::args::ToArgs, which resolves in this crate.

ToArgs

pub trait ToArgs {
    fn to_args(self) -> CallArgs;
}

Projects a typed args struct into the CallArgs host callbacks expect. Consumes self to avoid cloning owned fields.

Inverse of monty’s internal FromArgs (ArgValues → struct); driven by os::OsFunctionCall::to_args for the Python and JavaScript bindings.

to_args

Consumes the fields into arguments for delivery to the host.

format

pub mod format;

Pure CPython-compatible formatting helpers shared by the boundary types: string/bytes repr() escaping, shortest-round-trip float rendering, and timezone-offset timedelta reprs.

string_repr_fmt

pub fn string_repr_fmt(s: &str, f: &mut impl Write) -> fmt::Result;

Writes a Python repr() string for a given string slice to a formatter.

Quote choice matches CPython: single quotes by default, switching to double quotes only when the string contains a ' but no " (so the quote needn’t be escaped). Backslash, the active quote, and \n/\t/\r use the short escapes; any other non-printable character is escaped numerically (\xNN/\uNNNN/\UNNNNNNNN), e.g. repr('\x00') == "'\\x00'" and repr('\xa0') == "'\\xa0'".

“Non-printable” matches CPython’s str.isprintable (see repr_needs_escape): Unicode categories C* and Z*, except the ASCII space. Category data comes from unicode-general-category, whose Unicode version may differ slightly from CPython’s, affecting only recently (re)assigned code points.

StringRepr

pub struct StringRepr<'a>(pub &'a str);

Formatter for a Python repr() string.

Implements: Debug, Display.

bytes_repr_fmt

pub fn bytes_repr_fmt(bytes: &[u8], f: &mut impl Write) -> fmt::Result;

Writes a CPython-compatible repr string for bytes to a formatter.

Format: b'...' or b"..." depending on content.

  • Uses single quotes by default
  • Switches to double quotes if bytes contain ' but not "
  • Escapes: \\, \t, \n, \r, \xNN for non-printable bytes

bytes_repr

pub fn bytes_repr(bytes: &[u8]) -> String;

Returns a CPython-compatible repr string for bytes.

Convenience wrapper around bytes_repr_fmt that returns an owned String.

FormatFloat

pub struct FormatFloat(pub f64);

A Display adapter that writes a float exactly as CPython’s repr()/str() (identical for floats in Python 3): the shortest decimal string that round-trips, switching to scientific notation when the base-10 exponent is < -4 or >= 16, and always keeping at least one fractional digit (1.0, never 1) — 1e16"1e+16", 1234.5"1234.5", inf/nan lowercased.

This is the default rendering for a bare f"{x}", str(x), repr(x) and floats inside container reprs — not the format mini-language (that’s format_float_g et al, in monty). Rust can’t do this directly: its f64 Display never uses scientific notation (1e16 prints as 10000000000000000) and renders NaN as "NaN".

As a Display adapter it writes straight to the caller’s sink with no heap allocation: it borrows Rust’s shortest-digits guarantee via {:e} into a small stack buffer (an f64 {:e} is ASCII and ≤ 24 bytes) and re-lays-out those digits per CPython’s rules.

Implements: Display.

utf8_error_reason

pub fn utf8_error_reason(first_bad_byte: u8, error_len: Option<usize>) -> &'static str;

Classifies an invalid-UTF-8 error into CPython’s reason wording, from the first unexpected byte and Utf8Error::error_len().

error_len == None means the input ended mid-sequence (unexpected end of data); otherwise a byte that is a legal multi-byte lead (0xC2–0xF4) was followed by an invalid continuation, and anything else (stray continuation bytes, the overlong leads 0xC0/0xC1, 0xF5–0xFF) is an invalid start byte. Public (re-exported at the crate root) so monty-fs produces identical wording for text-mode file reads.

format_offset_timedelta_repr

pub fn format_offset_timedelta_repr(offset_seconds: i32) -> String;

Formats the canonical datetime.timedelta(...) repr for a fixed timezone offset in seconds, normalized like Python’s timedelta (days may be negative, seconds in 0..86400) — e.g. -18000datetime.timedelta(days=-1, seconds=68400). Used by the datetime.timezone reprs of boundary values.

MONTY_VERSION

pub const MONTY_VERSION: &str = "1.0.0-beta.2";

The monty version this build was compiled as.

BuiltinsFunctions

pub enum BuiltinsFunctions {
    Abs,
    All,
    Any,
    Bin,
    Chr,
    Divmod,
    Enumerate,
    Filter,
    Getattr,
    Hasattr,
    Hash,
    Hex,
    Id,
    Isinstance,
    Len,
    Map,
    Max,
    Min,
    Next,
    Oct,
    Open,
    Ord,
    Pow,
    Print,
    Repr,
    Reversed,
    Round,
    Setattr,
    Sorted,
    Sum,
    Type,
    Zip,
    /// `object.__setattr__(obj, name, value)` — the write that bypasses a
    /// class's attribute hooks, reached only through `object`. Its name is not
    /// an identifier, so it can never resolve as a bare global.
    ///
    /// The first variant whose name is not its lowercased identifier, so it
    /// needs both renames: serde and strum each carry the name across a
    /// different boundary (JSON vs. `Display`/`FromStr`) and must agree.
    ObjectSetattr,
    /// `format(value, format_spec='')`, appended after `Self::ObjectSetattr`.
    Format,
    /// `eval(source, /, globals=None, locals=None)`, appended after `Self::Format`.
    Eval,
    /// `exec(source, /, globals=None, locals=None, *, closure=None)`, appended after `Self::Eval`.
    Exec,
    /// `locals()`, appended after `Self::Exec`.
    Locals,
}

Enumerates every interpreter-native Python builtin function.

Listed alphabetically per https://docs.python.org/3/library/functions.html Commented-out variants are not yet implemented.

Note: Type constructors are handled by the Type enum, not here.

Uses strum derives for automatic Display, FromStr, and IntoStaticStr implementations. All variants serialize to lowercase (e.g., Print -> “print”).

from_repr

pub const fn from_repr(discriminant: u8) -> Option<BuiltinsFunctions>

Try to create Self from the raw representation

Implements: Clone, Copy, Debug, Deserialize<'de>, Display, Eq, From<&'_derivative_strum BuiltinsFunctions>, From<BuiltinsFunctions>, FromStr, Hash, PartialEq, Serialize, StructuralPartialEq, TryFrom<&str>, VariantNames.

CodeLoc

pub struct CodeLoc {
    /// Line number (1-based).
    pub line: u32,
    /// Column number (1-based), counted in characters (not bytes).
    pub column: u32,
}

A line and column position in source code.

Uses 1-based indexing for both line and column to match Python’s conventions.

u32 matches ruff_text_size::TextSize, which underpins all source ranges returned by the parser, so conversions between the two are zero-cost.

new

pub fn new(line: u32, column: u32) -> Self

Creates a new CodeLoc from 0-based values.

Lines and columns numbers are 1-indexed for display, hence + 1. Saturates at u32::MAX rather than panicking — overflow here is already unreachable for any source ruff will accept (it caps source size at 4 GiB), and saturation keeps the parser panic-free even if that ever changes.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, Hash, PartialEq, Serialize, StructuralPartialEq.

ExcData

pub enum ExcData {
    /// No structured payload — every exception type without a variant below.
    None,
    /// `UnicodeDecodeError` / `UnicodeEncodeError` constructor fields.
    /// Boxed to keep the common `None` case (and every exception embedding
    /// this enum) small.
    Unicode(Box<UnicodeErrorData>),
    /// `json.JSONDecodeError` attribute fields. Boxed like
    /// `ExcData::Unicode` to keep the enum small.
    Json(Box<JsonErrorData>),
}

Structured payload attached to exception types whose CPython counterparts carry more than a message. Currently unicode and json decode errors have one; the enum leaves room for future variants (e.g. OSError’s errno/filename) without another field on every exception.

unicode

pub fn unicode(&self) -> Option<&UnicodeErrorData>

The unicode-error fields, if this is ExcData::Unicode.

json

pub fn json(&self) -> Option<&JsonErrorData>

The json-error fields, if this is ExcData::Json.

Implements: Clone, Debug, Default, Deserialize<'de>, Hash, PartialEq, Serialize, StructuralPartialEq.

ExcType

pub enum ExcType {
    /// primary exception class - matches any exception in isinstance checks.
    ///
    /// Also the `Default` — required so `Type` (which embeds an `ExcType` in
    /// its `Exception` variant) can derive `strum::EnumIter`.
    Exception,
    /// System exit exceptions
    BaseException,
    SystemExit,
    KeyboardInterrupt,
    /// Intermediate class for arithmetic errors.
    ArithmeticError,
    /// Subclass of ArithmeticError.
    OverflowError,
    /// Subclass of ArithmeticError.
    ZeroDivisionError,
    /// Intermediate class for lookup errors.
    LookupError,
    /// Subclass of LookupError.
    IndexError,
    /// Subclass of LookupError.
    KeyError,
    /// Intermediate class for runtime errors.
    RuntimeError,
    /// Subclass of RuntimeError.
    NotImplementedError,
    /// Subclass of RuntimeError.
    RecursionError,
    AttributeError,
    /// Subclass of AttributeError (from dataclasses module).
    FrozenInstanceError,
    NameError,
    /// Subclass of NameError - for accessing local variable before assignment.
    UnboundLocalError,
    ValueError,
    /// Subclass of ValueError - for encoding/decoding errors.
    UnicodeDecodeError,
    /// Subclass of ValueError - for encoding errors (e.g. `str.encode('ascii')`
    /// on a string containing non-ASCII characters).
    UnicodeEncodeError,
    /// Subclass of ValueError for invalid JSON syntax in `json.loads()`.
    JsonDecodeError,
    /// Import-related errors (module not found, name not in module).
    ImportError,
    /// Subclass of ImportError - for when a module cannot be found.
    ModuleNotFoundError,
    /// OS-related errors (file not found, permission denied, etc.)
    OSError,
    /// Subclass of OSError - for when a file or directory cannot be found.
    FileNotFoundError,
    /// Subclass of OSError - for when a file already exists.
    FileExistsError,
    /// Subclass of OSError - for when a path is a directory but a file was expected.
    IsADirectoryError,
    /// Subclass of OSError - for when a path is not a directory but one was expected.
    NotADirectoryError,
    /// Subclass of OSError - for when an operation is not permitted (e.g., writing
    /// to a read-only mount, or attempting to access a path outside a mounted directory).
    PermissionError,
    /// `io.UnsupportedOperation` - raised by file objects when a requested
    /// operation isn't allowed by the open mode (e.g. `read()` on `'w'`).
    ///
    /// In CPython this inherits from both `OSError` and `ValueError`. Monty's
    /// `ExcType` enum models single parents, but `Self::is_subclass_of`
    /// matches `UnsupportedOperation` against both `OSError` and `ValueError`
    /// so `except ValueError:` and `except OSError:` both catch it as in
    /// CPython.
    UnsupportedOperation,
    /// Subclass of OSError since Python 3.3 (PEP 3151).
    TimeoutError,
    AssertionError,
    MemoryError,
    StopIteration,
    SyntaxError,
    TypeError,
    /// `re.PatternError` - raised for invalid regex patterns or unsupported regex features.
    ///
    /// # Behavior Note
    ///
    /// Limited to monty's exception type, `PatternError` does not provide `pattern`, `pos`,
    /// `lineno` and `colno` attributes.
    ///
    /// As per CPython's implementation, it would be hard to convert `fancy-regex`'s error
    /// representations into the required attributes.
    RePatternError,
    /// `binascii.Error` - raised by the `base64` codecs for malformed input.
    ///
    /// A `ValueError` subclass in CPython, so `except ValueError:` catches it.
    BinasciiError,
    /// `binascii.Incomplete` - a direct `Exception` subclass, not a `ValueError`.
    ///
    /// Nothing raises it: the `a2b_hqx` family it belonged to left CPython in
    /// 3.11, so the class survives only for `except binascii.Incomplete:` in
    /// older code. Monty exposes it for the same reason.
    BinasciiIncomplete,
}

Python exception types supported by the interpreter.

Uses strum derives for automatic Display, FromStr, and Into<&'static str> implementations. The string representation matches the variant name exactly (e.g., ValueError -> “ValueError”), except where a serialize attribute gives a dotted name to disambiguate a stdlib subclass from its builtin parent (binascii.Error from ValueError, say).

ExcType::VARIANTS is the canonical list of those names, and the host bridges mirror it: PYTHON_EXC_NAMES in crates/monty-js/ts/errors.ts and the ExcType literal in pydantic_monty/__init__.py. Both are hand-written, so monty-proto’s exc_type_bridges test reads them and pins them here — adding a variant without wiring the bridges fails that test.

is_subclass_of

pub fn is_subclass_of(self, handler_type: Self) -> bool

Checks if this exception type is a subclass of another exception type.

Implements Python’s exception hierarchy for try/except matching:

  • Exception is the base class for all standard exceptions
  • LookupError is the base for KeyError and IndexError
  • ArithmeticError is the base for ZeroDivisionError and OverflowError
  • RuntimeError is the base for RecursionError and NotImplementedError

Returns true if self would be caught by except handler_type:.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Display, Eq, From<&'_derivative_strum ExcType>, From<ExcType>, FromStr, Hash, PartialEq, Serialize, StructuralPartialEq, TryFrom<&str>, VariantNames.

JsonErrorData

pub struct JsonErrorData {
    /// The bare error message, without the `: line N column M (char K)`
    /// suffix the formatted exception message carries.
    pub msg: String,
    /// The document being parsed, matching CPython's `exc.doc`. `None` when
    /// the document exceeds `JsonErrorData::MAX_DOC_LEN` or is not valid
    /// UTF-8 (`json.loads` on `bytes` input).
    pub doc: Option<String>,
    /// Character index of the error in `doc`, matching CPython's `exc.pos`.
    pub pos: usize,
    /// 1-based line of the error, matching CPython's `exc.lineno`.
    pub lineno: usize,
    /// 1-based column of the error, matching CPython's `exc.colno`.
    pub colno: usize,
}

Structured fields of a json.JSONDecodeError, mirroring CPython’s msg / doc / pos / lineno / colno exception attributes.

As with UnicodeErrorData, the payload exists so host bindings can construct a real json.JSONDecodeError instead of falling back to a plain ValueError; sandboxed code never sees these fields. lineno/colno are carried explicitly rather than recomputed from doc because doc may be absent (see JsonErrorData::MAX_DOC_LEN).

MAX_DOC_LEN

pub const MAX_DOC_LEN: usize = _;

Document size cap, mirroring UnicodeErrorData::MAX_OBJECT_LEN: exception payloads are copied into the host once they escape the worker, so doc is dropped (not truncated — a partial document would misplace pos) for larger inputs.

build

pub fn build(msg: &str, doc: &[u8], pos: usize, lineno: usize, colno: usize) -> ExcData

Builds the payload for a decode error on doc, omitting the document when it exceeds Self::MAX_DOC_LEN or is not valid UTF-8.

Implements: Clone, Debug, Deserialize<'de>, Hash, PartialEq, Serialize, StructuralPartialEq.

MontyException

pub struct MontyException { /* private fields */ }

Public representation of a Monty exception.

new

pub fn new(exc_type: ExcType, message: Option<String>) -> Self

Create a new MontyException with the given exception type and message.

You can’t provide a traceback here, it’s send when raising the exception.

with_traceback

pub fn with_traceback(
    exc_type: ExcType,
    message: Option<String>,
    traceback: Vec<StackFrame>,
) -> Self

Creates an exception with an explicit traceback.

Most callers should use MontyException::new — the traceback is normally attached when the exception is raised. This constructor exists for boundaries that reconstruct an exception that was raised elsewhere (e.g. deserializing one received from a monty subprocess worker) and must preserve its original frames.

with_data

pub fn with_data(self, data: ExcData) -> Self

Attaches a structured payload — see ExcData. Public for boundaries that reconstruct an exception raised elsewhere (like MontyException::with_traceback); in-process raises attach the payload at the raise site instead.

data

pub fn data(&self) -> &ExcData

The structured payload, ExcData::None for most exceptions.

unicode_data

pub fn unicode_data(&self) -> Option<&UnicodeErrorData>

Structured UnicodeDecodeError/UnicodeEncodeError fields, present only for unicode errors raised by codec operations on objects no larger than UnicodeErrorData::MAX_OBJECT_LEN.

json_data

pub fn json_data(&self) -> Option<&JsonErrorData>

Structured json.JSONDecodeError fields, present only for decode errors raised by json.loads (not for manually raised exceptions).

take_data

pub fn take_data(&mut self) -> ExcData

Removes and returns the structured payload, for consumers (like the Python bindings) that rebuild the native exception and want the payload by value without cloning it.

add_traceback

pub fn add_traceback(&mut self, traceback: impl IntoIterator<Item = StackFrame>)

Appends frames to this exception’s traceback.

runtime_error

pub fn runtime_error(err: impl fmt::Display) -> Self

Shorthand for a traceback-free RuntimeError wrapping err’s display output — used at host boundaries (input conversion, REPL feeds) where no sandbox stack frames exist.

exc_type

pub fn exc_type(&self) -> ExcType

The exception type raised.

message

pub fn message(&self) -> Option<&str>

Optional exception message explaining what went wrong.

Equivalent of python’s exc.args[0]

into_message

pub fn into_message(self) -> Option<String>

Optional exception message explaining what went wrong.

This takes ownership of the MontyException and returns an owned String.

Equivalent of python’s exc.args[0]

traceback

pub fn traceback(&self) -> &[StackFrame]

Stack trace of the exception, first is the outermost frame shown first in the traceback

summary

pub fn summary(&self) -> String

Returns a compact summary of the exception.

Format: ExceptionType: message (e.g., NotImplementedError: feature not supported) If there’s no message, just returns the exception type name.

py_repr

pub fn py_repr(&self) -> String

Returns the exception formatted as Python’s repr() would display it.

Format: ExceptionType('message') (e.g., ValueError('invalid value')) Uses appropriate quoting for messages containing quotes.

Implements: Clone, Debug, Deserialize<'de>, Display, Error, From<MontyException>, PartialEq, Serialize, StructuralPartialEq.

StackFrame

pub struct StackFrame {
    /// The filename where the code is located.
    pub filename: String,
    /// Start position in the source code.
    pub start: CodeLoc,
    /// End position in the source code.
    pub end: CodeLoc,
    /// The name of the frame (function name, or None for module-level code).
    pub frame_name: Option<String>,
    /// The source code line for preview in the traceback.
    ///
    /// Stored as `Arc<str>` rather than `String` so that consecutive frames
    /// referencing the same source line — typical of recursion and tight
    /// helper-function loops — share a single allocation. Without sharing, a
    /// 1000-deep recursive call into code on a long line would clone the
    /// entire line into each frame and amplify memory usage by the call
    /// depth. Serialization roundtrips lose the sharing (each frame gets
    /// its own `Arc`), but that is bounded by the wire size of the
    /// traceback so does not regress the amplification.
    pub preview_line: Option<std::sync::Arc<str>>,
    /// Whether to hide the caret marker in the traceback for this frame.
    ///
    /// Set to `true` for:
    /// - `raise` statements (CPython doesn't show carets for raise)
    /// - `AttributeError` on attribute access (CPython doesn't show carets for these)
    pub hide_caret: bool,
    /// Whether to hide the `, in <name>` part of the frame line.
    ///
    /// Set to `true` for `SyntaxError` where CPython doesn't show the frame name.
    /// CPython's SyntaxError format: `  File "...", line N`
    /// vs runtime error format: `  File "...", line N, in <module>`
    pub hide_frame_name: bool,
}

A single frame in a Python traceback.

Contains all the information needed to display a traceback line: the file location, function name, and optional source code preview.

Caret Markers

Monty uses only ~ characters for caret markers in tracebacks, unlike CPython 3.11+ which uses ~ for the function name and ^ for arguments (e.g., ~~~~~~~~~~~^^^^^^^^^^^). This simplification is intentional - Monty marks the entire expression span uniformly.

Implements: Clone, Debug, Deserialize<'de>, Display, PartialEq, Serialize, StructuralPartialEq.

UnicodeErrorData

pub struct UnicodeErrorData {
    /// The codec name as CPython reports it, e.g. `"utf-8"`, `"ascii"`.
    pub encoding: String,
    /// The full input that failed to encode/decode (`str` for encode errors,
    /// `bytes` for decode errors), matching CPython's `exc.object`.
    pub object: UnicodeErrorObject,
    /// Start of the failing range: a character index for encode errors, a
    /// byte offset for decode errors.
    pub start: usize,
    /// Exclusive end of the failing range, in the same units as `start`.
    pub end: usize,
    /// CPython's reason wording, e.g. `"ordinal not in range(128)"`.
    pub reason: String,
}

Structured fields of a UnicodeDecodeError / UnicodeEncodeError, mirroring CPython’s encoding / object / start / end / reason exception attributes.

Monty exceptions are otherwise message-only; unicode errors additionally carry these fields so host bindings (e.g. pydantic_monty) can construct real UnicodeDecodeError / UnicodeEncodeError instances instead of falling back to a plain ValueError. The payload is omitted when the offending object is larger than UnicodeErrorData::MAX_OBJECT_LEN — exceptions can be stored and copied outside the sandbox’s resource tracker, so an unbounded payload would let huge inputs evade memory limits. Sandboxed code never sees these fields (in-sandbox exceptions expose only args).

MAX_OBJECT_LEN

pub const MAX_OBJECT_LEN: usize = _;

Payload size cap: unicode errors on objects larger than this carry no structured data (hosts fall back to the message-only ValueError). Exception payloads are copied into the host once they escape the worker, so the cap bounds how much host memory a single raise can pin.

encode

pub fn encode(
    encoding: &str,
    object: &str,
    start: usize,
    end: usize,
    reason: &str,
) -> ExcData

Builds the payload for an encode error on object, or ExcData::None when object exceeds Self::MAX_OBJECT_LEN.

decode

pub fn decode(
    encoding: &str,
    object: &[u8],
    start: usize,
    end: usize,
    reason: &str,
) -> ExcData

Builds the payload for a decode error on object, or ExcData::None when object exceeds Self::MAX_OBJECT_LEN. Public so monty-fs can build the payload for text-mode file reads.

Implements: Clone, Debug, Deserialize<'de>, Hash, PartialEq, Serialize, StructuralPartialEq.

UnicodeErrorObject

pub enum UnicodeErrorObject {
    /// A decode error's input `bytes`.
    Bytes(Vec<u8>),
    /// An encode error's input `str`.
    Str(String),
}

The object attribute of a unicode error: the input being converted.

Implements: Clone, Debug, Deserialize<'de>, Hash, PartialEq, Serialize, StructuralPartialEq.

unicode_decode_error_msg

pub fn unicode_decode_error_msg(
    codec: &str,
    first_byte: u8,
    start: usize,
    end: usize,
    reason: &str,
) -> String;

Formats the message for a UnicodeDecodeError covering the byte range start..end: CPython’s single-byte form (byte 0x\{first_byte:02x\} in position \{start\}) when the range is one byte, otherwise the range form (bytes in position {start}-{end - 1}).

A free function (rather than folded into ExcType::unicode_decode_error), public and re-exported at the crate root, so monty-fs can produce the identical wording when converting a MountError::InvalidUtf8 from a text-mode file read into an exception.

FileMode

pub enum FileMode {
    /// `r` / `rb`: read-only; the file must already exist.
    Read(bool),
    /// `r+` / `rb+`: read and write an existing file. Reserved; not yet
    /// produced by `FromStr`.
    ReadUpdate(bool),
    /// `w` / `wb`: write-only; truncate the file (creating it if missing) on open.
    Write(bool),
    /// `w+` / `wb+`: read and write; truncate the file (creating it if missing).
    /// Reserved; not yet produced by `FromStr`.
    WriteUpdate(bool),
    /// `a` / `ab`: write-only appending; create the file if missing, preserving content.
    Append(bool),
    /// `a+` / `ab+`: read and append; create the file if missing, preserving content.
    /// Reserved; not yet produced by `FromStr`.
    AppendUpdate(bool),
}

A parsed Python open() mode.

This single enum captures everything that matters about how a file was opened: the access pattern (r/w/a and the + update flag) and whether the file is binary. The variant name encodes the access pattern; the bool payload is true for binary and false for text — i.e. Read(true) is 'rb' and Read(false) is 'r'.

Construct one with the FromStr impl (mode_str.parse::<FileMode>()). The original input string is intentionally not preserved; FileMode::as_str rebuilds the canonical CPython form ('r', 'rb+', 'wb', …), matching how CPython itself normalizes input like 'rt''r' and 'r+b''rb+'.

+ update modes (ReadUpdate/WriteUpdate/AppendUpdate) are reserved in the enum so the mode space is fully represented, but FromStr currently rejects them — properly modelling them needs read-position tracking that the file wrapper does not yet implement. Treat the Update variants as unreachable at runtime; do not pattern-match against them as if they were a valid result of parsing user input.

Carried publicly by MontyFileHandle so a host servicing file operations can inspect the mode without re-parsing the raw string.

as_str

pub fn as_str(&self) -> &'static str

Returns the canonical Python open() mode string for this mode, matching what CPython exposes via file.mode.

The result is always one of the 12 well-formed mode strings (r, rb, r+, rb+, w, wb, w+, wb+, a, ab, a+, ab+). This is the canonical form CPython itself normalizes user input into — e.g. 'rt''r', 'r+b''rb+', 'br''rb'.

is_binary

pub fn is_binary(&self) -> bool

Whether the file is binary ('rb', 'wb', …) rather than text.

readable

pub fn readable(&self) -> bool

Whether read() is allowed by this mode.

writable

pub fn writable(&self) -> bool

Whether write() is allowed by this mode.

is_append

pub fn is_append(&self) -> bool

Whether writes should always append (a/a+).

truncate

pub fn truncate(&self) -> bool

Whether open() must truncate the file to empty immediately (w/w+).

create

pub fn create(&self) -> bool

Whether open() must create the file immediately if missing.

True for the w/w+ and a/a+ families. For append modes this must not disturb existing content.

type_name

pub fn type_name(&self) -> &'static str

Returns the bare Python type name (type(f).__name__) for this mode.

file_type_name

pub fn file_type_name(&self) -> &'static str

Returns the fully-qualified _io wrapper type name a file opened with this mode presents as, matching CPython’s repr(f) (e.g. "_io.TextIOWrapper"). The module-less form is type_name.

Implements: Clone, Copy, Debug, Deserialize<'de>, Eq, FromStr, Hash, PartialEq, PushValue, Serialize, StructuralPartialEq.

FormatFloat

pub struct FormatFloat(pub f64);

A Display adapter that writes a float exactly as CPython’s repr()/str() (identical for floats in Python 3): the shortest decimal string that round-trips, switching to scientific notation when the base-10 exponent is < -4 or >= 16, and always keeping at least one fractional digit (1.0, never 1) — 1e16"1e+16", 1234.5"1234.5", inf/nan lowercased.

This is the default rendering for a bare f"{x}", str(x), repr(x) and floats inside container reprs — not the format mini-language (that’s format_float_g et al, in monty). Rust can’t do this directly: its f64 Display never uses scientific notation (1e16 prints as 10000000000000000) and renders NaN as "NaN".

As a Display adapter it writes straight to the caller’s sink with no heap allocation: it borrows Rust’s shortest-digits guarantee via {:e} into a small stack buffer (an f64 {:e} is ASCII and ≤ 24 bytes) and re-lays-out those digits per CPython’s rules.

Implements: Display.

StringRepr

pub struct StringRepr<'a>(pub &'a str);

Formatter for a Python repr() string.

Implements: Debug, Display.

bytes_repr

pub fn bytes_repr(bytes: &[u8]) -> String;

Returns a CPython-compatible repr string for bytes.

Convenience wrapper around bytes_repr_fmt that returns an owned String.

bytes_repr_fmt

pub fn bytes_repr_fmt(bytes: &[u8], f: &mut impl Write) -> fmt::Result;

Writes a CPython-compatible repr string for bytes to a formatter.

Format: b'...' or b"..." depending on content.

  • Uses single quotes by default
  • Switches to double quotes if bytes contain ' but not "
  • Escapes: \\, \t, \n, \r, \xNN for non-printable bytes

string_repr_fmt

pub fn string_repr_fmt(s: &str, f: &mut impl Write) -> fmt::Result;

Writes a Python repr() string for a given string slice to a formatter.

Quote choice matches CPython: single quotes by default, switching to double quotes only when the string contains a ' but no " (so the quote needn’t be escaped). Backslash, the active quote, and \n/\t/\r use the short escapes; any other non-printable character is escaped numerically (\xNN/\uNNNN/\UNNNNNNNN), e.g. repr('\x00') == "'\\x00'" and repr('\xa0') == "'\\xa0'".

“Non-printable” matches CPython’s str.isprintable (see repr_needs_escape): Unicode categories C* and Z*, except the ASCII space. Category data comes from unicode-general-category, whose Unicode version may differ slightly from CPython’s, affecting only recently (re)assigned code points.

utf8_error_reason

pub fn utf8_error_reason(first_bad_byte: u8, error_len: Option<usize>) -> &'static str;

Classifies an invalid-UTF-8 error into CPython’s reason wording, from the first unexpected byte and Utf8Error::error_len().

error_len == None means the input ended mid-sequence (unexpected end of data); otherwise a byte that is a legal multi-byte lead (0xC2–0xF4) was followed by an invalid continuation, and anything else (stray continuation bytes, the overlong leads 0xC0/0xC1, 0xF5–0xFF) is an invalid start byte. Public (re-exported at the crate root) so monty-fs produces identical wording for text-mode file reads.

COLLECT_STREAMS_ENTRY_OVERHEAD

pub const COLLECT_STREAMS_ENTRY_OVERHEAD: usize = 64;

Host bytes charged for each retained (stream, text) entry beyond its text.

A retained entry costs about 32 bytes in the vector plus its String’s own allocation, none of which is text. Charging only the text would let a run that starts a fresh entry per byte — one that alternates between the two streams — occupy roughly 64x the host memory the cap accounts for.

CollectedStreams

pub struct CollectedStreams { /* private fields */ }

The buffer behind PrintWriter::CollectStreams: (stream, text) entries plus the running charge their cap is checked against.

The charge is carried rather than re-derived because the cap is checked on every fragment: summing the entries each time is O(entries), which a run alternating between the streams turns into quadratic work, since each switch starts a new entry. pydantic_monty’s collector keeps the same running charge for the same reason.

entries

pub fn entries(&self) -> &[(PrintStream, String)]

The collected fragments, in the order the sandbox produced them.

into_entries

pub fn into_entries(self) -> Vec<(PrintStream, String)>

Takes the collected fragments, leaving the buffer empty.

Implements: Debug, Default.

DEFAULT_MAX_PRINT_COLLECT_BYTES

pub const DEFAULT_MAX_PRINT_COLLECT_BYTES: usize = 10_485_760usize;

Default cap for PrintWriter::CollectString / PrintWriter::CollectStreams and the matching Python collectors.

Host-side print buffers sit outside ResourceLimits::max_memory; without a cap, a print loop can OOM the host while sandbox limits stay green. Pass max_bytes: None to opt out on trusted hosts.

PrintStream

pub enum PrintStream {
    /// Standard output, the default for a `print()` call with no `file=`.
    Stdout,
    /// Standard error, reached by `print(..., file=sys.stderr)`.
    Stderr,
}

Identifies the output stream for a single print fragment.

print() writes to Stdout unless it is given file=sys.stderr, which is the only way sandboxed code can reach Stderr.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq.

PrintWriter

pub enum PrintWriter<'a> {
    /// Silently discard all output.
    Disabled,
    /// Write to standard output.
    Stdout,
    /// Collect all output into a single `String`, in emit order, with no stream labels.
    ///
    /// Second field: max collected bytes (`None` = unlimited). Exceeding raises
    /// `MemoryError` with the same message as `ResourceError::Memory`.
    CollectString(&'a mut String, Option<usize>),
    /// Collect all output as `(stream, text)` entries.
    ///
    /// The builtin `print()` implementation calls `write` for each argument and
    /// `push` for each separator/terminator. To avoid one entry per fragment,
    /// `CollectedStreams` appends to the trailing entry when it already
    /// matches the current stream; a new entry is only pushed when the stream
    /// changes. So a run that stays on one stream collects a single entry
    /// however many fragments it wrote, and one that alternates collects one
    /// per run.
    ///
    /// Second field: the cap the collector's charge is checked against (`None`
    /// = unlimited), which counts `COLLECT_STREAMS_ENTRY_OVERHEAD` per entry
    /// as well as the text.
    CollectStreams(&'a mut CollectedStreams, Option<usize>),
    /// Delegate to a custom callback.
    Callback(&'a mut dyn PrintWriterCallback),
}

Output handler for the print() builtin function.

Provides common output modes as enum variants to avoid trait object overhead in the typical cases (stdout, disabled, collect). For custom output handling, use the Callback variant with a PrintWriterCallback implementation.

Variants

  • Disabled — silently discards all output (useful for benchmarking or suppressing output).
  • Stdout — writes to standard output (the default behavior).
  • CollectString — accumulates output into a target String for programmatic access. No stream labels are preserved; every fragment is appended in the order it was emitted. The Option<usize> is an optional byte cap (None = unlimited); constructors collect_string / default Python collectors use DEFAULT_MAX_PRINT_COLLECT_BYTES.
  • CollectStreams — accumulates output as (stream, text) pairs, merging consecutive same-stream fragments into one tuple. Each write to the same stream extends the trailing entry rather than producing a new one; a new tuple is only pushed when the stream changes. Same optional byte cap as CollectString.
  • Callback — delegates to a user-provided PrintWriterCallback implementation.

collect_string

pub fn collect_string(buf: &mut String) -> PrintWriter<'_>

Collect into buf with the default DEFAULT_MAX_PRINT_COLLECT_BYTES cap.

collect_streams

pub fn collect_streams(buf: &mut CollectedStreams) -> PrintWriter<'_>

Collect into buf with the default DEFAULT_MAX_PRINT_COLLECT_BYTES cap.

reborrow

pub fn reborrow(&mut self) -> PrintWriter<'_>

Creates a new PrintWriter that reborrows the same underlying target.

This is useful in iterative execution (start/resume loops) where each step takes PrintWriter by value but you want all steps to write to the same output target. The original writer remains valid after the reborrowed copy is dropped.

write

pub fn write(
    &mut self,
    stream: PrintStream,
    output: Cow<'_, str>,
) -> Result<(), MontyException>

Called once for each formatted argument passed to print().

This method writes only the given argument’s text, without adding separators or a trailing newline. Separators (spaces) and the final terminator (newline) are emitted via push.

CollectString keeps no stream labels, so a run that prints to both streams interleaves them in one buffer. Use CollectStreams to tell them apart.

push

pub fn push(&mut self, stream: PrintStream, end: char) -> Result<(), MontyException>

Appends a single character to the given stream.

Generally called to add spaces (separators) and newlines (terminators) within print output.

stdout_write

pub fn stdout_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException>

write to PrintStream::Stdout.

stdout_push

pub fn stdout_push(&mut self, end: char) -> Result<(), MontyException>

push to PrintStream::Stdout.

wants_poll

pub fn wants_poll(&self) -> bool

Whether this writer wants poll_flush called at all.

Only Callback can buffer, so the VM hoists this out of its dispatch loop and skips the poll (and its clock read) entirely for every other variant.

poll_flush

pub fn poll_flush(&mut self) -> Result<(), MontyException>

Forwards the VM’s periodic checkpoint to a buffering callback.

PrintWriterCallback

pub trait PrintWriterCallback {
    fn stdout_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException>;
    fn stdout_push(&mut self, end: char) -> Result<(), MontyException>;
    fn stderr_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException> { ... }
    fn stderr_push(&mut self, end: char) -> Result<(), MontyException> { ... }
    fn poll_flush(&mut self) -> Result<(), MontyException> { ... }
}

Trait for custom output handling from the print() builtin function.

Implement this trait and pass it via PrintWriter::Callback to capture or redirect print output from sandboxed Python code.

stdout_write

Called once for each formatted argument passed to print().

This method is responsible for writing only the given argument’s text, and must not add separators or a trailing newline. Separators (such as spaces) and the final terminator (such as a newline) are emitted via stdout_push.

Arguments

  • output - The formatted output string for a single argument (without separators or trailing newline).

stdout_push

Add a single character to stdout.

Generally called to add spaces and newlines within print output.

Arguments

  • end - The character to print after the formatted output.

stderr_write

Called for each formatted argument of a print(..., file=sys.stderr).

Defaults to stdout_write, so a host written before stderr existed still receives the text instead of losing it.

stderr_push

Adds a single character to stderr. Defaults to stdout_push, as stderr_write does.

poll_flush

Gives a buffering implementation a chance to release what it holds.

The VM calls this from its periodic dispatch checkpoint, so a callback that batches writes can bound how long output sits unsent while the program computes without printing. Implementations that write straight through do nothing here.

check_print_collect_limit

pub fn check_print_collect_limit(
    current_len: usize,
    add: usize,
    max_bytes: Option<usize>,
) -> Result<(), MontyException>;

Rejects a collect-buffer growth that would exceed max_bytes.

None means unlimited. On overflow, returns the same MemoryError message as ResourceError::Memory so hosts see one familiar limit string.

CallArgs

pub struct CallArgs { /* private fields */ }

The positional and keyword arguments of one function or OS call. A sub-object shared within an exported call stays shared at the host boundary.

new

pub fn new() -> Self

No arguments.

push_arg

pub fn push_arg(&mut self, value: MontyObject)

Appends a positional argument.

push_kwarg

pub fn push_kwarg(&mut self, name: &str, value: MontyObject)

Appends a keyword argument with a string key.

arg

pub fn arg(&self, index: usize) -> Option<ObjectRef<'_>>

The indexth positional argument.

args

pub fn args(&self) -> impl ExactSizeIterator<Item = ObjectRef<'_>>

The positional arguments, in order.

kwargs

pub fn kwargs(&self) -> impl ExactSizeIterator<Item = (ObjectRef<'_>, ObjectRef<'_>)>

The keyword arguments as (key, value) views, in order.

kwarg

pub fn kwarg(&self, name: &str) -> Option<ObjectRef<'_>>

The keyword argument named name, if present.

Implements: Clone, Debug, Default, Deserialize<'de>, Eq, From<(Vec<MontyObject>, Vec<(MontyObject, MontyObject)>)>, From<Vec<MontyObject>>, PartialEq, Serialize, StructuralPartialEq.

ConversionError

pub struct ConversionError {
    /// The type name that was expected (e.g., "int", "str").
    pub expected: &'static str,
    /// The actual type name of the value (e.g., "list", "NoneType", or a
    /// class instance's class name).
    pub actual: String,
}

Error returned when a MontyObject cannot be converted to the requested Rust type.

Returned by the TryFrom implementations when an ObjectRef holds a different kind of value than the one requested.

new

pub fn new(expected: &'static str, actual: impl Into<String>) -> Self

Creates a new ConversionError with the expected and actual type names.

Implements: Debug, Display, Error.

InvalidInputError

pub enum InvalidInputError {
    /// The input type is not valid for conversion to a runtime Value.
    /// Message explaining why the type is invalid.
    InvalidType(std::borrow::Cow<'static, str>),
    /// A resource limit was exceeded during conversion.
    Resource(ResourceError),
}

Error returned when a value cannot be used as an input to code execution.

This can occur when:

  • A value (like MontyObject::repr) is only valid as an output, not an input
  • A resource limit is exceeded during conversion

invalid_type

pub fn invalid_type(msg: impl Into<Cow<'static, str>>) -> Self

Creates a new InvalidInputError for the given type name.

Implements: Clone, Debug, Display, Error, From<ResourceError>.

MAX_TIMEZONE_OFFSET_SECONDS

pub const MAX_TIMEZONE_OFFSET_SECONDS: i32 = 86_399;

Largest UTC offset datetime.timezone accepts, +23:59:59.

See MIN_TIMEZONE_OFFSET_SECONDS.

MIN_TIMEZONE_OFFSET_SECONDS

pub const MIN_TIMEZONE_OFFSET_SECONDS: i32 = -86_399;

Smallest UTC offset datetime.timezone accepts, -23:59:59.

CPython requires an offset strictly inside ±24 hours. Shared with the wire decoder so a forged offset is rejected at the boundary rather than by the sandbox-side constructor, which by then can only report a generic bad value.

MontyDate

pub struct MontyDate {
    /// Gregorian year in range 1..=9999.
    pub year: i32,
    /// Month component in range 1..=12.
    pub month: u8,
    /// Day component valid for the given month/year.
    pub day: u8,
}

A Python datetime.date value with year, month, and day components.

Implements: Clone, Debug, Deserialize<'de>, Eq, Hash, PartialEq, Serialize, StructuralPartialEq.

MontyDateTime

pub struct MontyDateTime {
    /// Gregorian year in range 1..=9999.
    pub year: i32,
    /// Month component in range 1..=12.
    pub month: u8,
    /// Day component valid for the given month/year.
    pub day: u8,
    /// Hour in range 0..=23.
    pub hour: u8,
    /// Minute in range 0..=59.
    pub minute: u8,
    /// Second in range 0..=59.
    pub second: u8,
    /// Microsecond in range 0..=999_999.
    pub microsecond: u32,
    /// Fixed offset seconds for aware datetimes, or `None` for naive values.
    ///
    /// Within `MIN_TIMEZONE_OFFSET_SECONDS`..=`MAX_TIMEZONE_OFFSET_SECONDS` when set.
    pub offset_seconds: Option<i32>,
    /// Optional explicit timezone name for aware datetimes.
    ///
    /// Must be `None` when `offset_seconds` is `None`.
    pub timezone_name: Option<String>,
}

A Python datetime.datetime value with date, time, and optional timezone components.

Implements: Clone, Debug, Deserialize<'de>, Eq, Hash, PartialEq, Serialize.

MontyFileHandle

pub struct MontyFileHandle {
    /// The virtual (sandbox) path of the file. Never a host path.
    pub path: String,
    /// The parsed `open()` mode.
    pub mode: FileMode,
    /// Position for sized/line/seek operations: char index in text mode,
    /// byte index in binary mode. `0` for a freshly opened file.
    pub position: u64,
}

An open file object (the result of open()).

This is the boundary representation of Monty’s heap OpenFile wrapper. It carries everything needed to service a file operation from a host that holds no live OS handle: the virtual path, the mode, and the byte position for seek-aware reads.

The host produces a FileHandle as the result of an OsFunctionCall::Open call; the interpreter then builds its heap file wrapper from it. Conversely, a heap file object passed as an argument to a read/write OS call is converted back to a FileHandle so the host receives this state.

Implements: Clone, Debug, Deserialize<'de>, Display, Serialize.

MontyObject

pub struct MontyObject { /* private fields */ }

One owned Python value at the host boundary.

Carried by Complete, resume results, name lookups and os.getenv defaults, and the type hosts build inputs with. Equality is structural as Python values, independent of storage layout.

none

pub fn none() -> Self

Python None.

ellipsis

pub fn ellipsis() -> Self

Python Ellipsis.

not_implemented

pub fn not_implemented() -> Self

Python NotImplemented.

bool

pub fn bool(value: bool) -> Self

A bool.

int

pub fn int(value: i64) -> Self

An int that fits in 64 bits.

bigint

pub fn bigint(value: BigInt) -> Self

An int of any size.

float

pub fn float(value: f64) -> Self

A float.

string

pub fn string(value: impl Into<String>) -> Self

A str.

bytes

pub fn bytes(value: impl Into<Vec<u8>>) -> Self

A bytes.

path

pub fn path(value: impl Into<String>) -> Self

A pathlib.Path, always a virtual POSIX path.

date

pub fn date(value: MontyDate) -> Self

A datetime.date.

datetime

pub fn datetime(value: MontyDateTime) -> Self

A datetime.datetime.

time

pub fn time(value: MontyTime) -> Self

A datetime.time.

timedelta

pub fn timedelta(value: MontyTimeDelta) -> Self

A datetime.timedelta.

timezone

pub fn timezone(value: MontyTimeZone) -> Self

A datetime.timezone.

exception

pub fn exception(exc_type: ExcType, arg: Option<String>) -> Self

An exception instance as a value (not raised), with its message.

function

pub fn function(name: impl Into<String>, docstring: Option<String>) -> Self

A host function the sandbox calls back by name.

builtin_function

pub fn builtin_function(function: BuiltinsFunctions) -> Self

A builtin function such as len.

type_object

pub fn type_object(value: MontyType) -> Self

A builtin type object such as int.

file_handle

pub fn file_handle(value: MontyFileHandle) -> Self

An open file object, as the result of an open() OS call.

repr

pub fn repr(value: impl Into<String>) -> Self

Output-only: a value’s repr() where no faithful representation exists.

cycle

pub fn cycle(placeholder: impl Into<String>) -> Self

Output-only: a reference back to an enclosing container, as its placeholder.

list

pub fn list(items: impl IntoIterator<Item = Self>) -> Self

A list.

tuple

pub fn tuple(items: impl IntoIterator<Item = Self>) -> Self

A tuple.

set

pub fn set(items: impl IntoIterator<Item = Self>) -> Self

A set.

frozenset

pub fn frozenset(items: impl IntoIterator<Item = Self>) -> Self

A frozenset.

dict

pub fn dict(pairs: impl IntoIterator<Item = (Self, Self)>) -> Self

A dict from (key, value) pairs, in insertion order.

named_tuple

pub fn named_tuple(
    type_name: impl Into<String>,
    field_names: impl IntoIterator<Item = impl Into<String>>,
    values: impl IntoIterator<Item = Self>,
) -> Self

A namedtuple: type_name(field=value, ...).

class_type

pub fn class_type(
    name: impl Into<String>,
    id: MontyUuid,
    host_defined: bool,
    is_dataclass: bool,
    attrs: impl IntoIterator<Item = (Self, Self)>,
) -> Self

A non-builtin class type object with its eager class attrs.

id is generated by whichever side defined the class (a host uuid4, or a worker uuid for sandbox classes); the sandbox keeps one type object per id and routes instantiation and classmethod calls by it.

class_instance

pub fn class_instance(
    class_type: Self,
    instance_id: MontyUuid,
    attrs: impl IntoIterator<Item = (Self, Self)>,
) -> Self

An instance of class_type (a class_type value) with its eager attrs, identified by instance_id.

Panics

If class_type is not a class type object.

builtin_function_from_name

pub fn builtin_function_from_name(name: &str) -> Option<Self>

Resolves a builtin function by its Python name (e.g. "len"), the name its Display renders.

as_ref

pub fn as_ref(&self) -> ObjectRef<'_>

Borrows the value for inspection without copying.

py_repr

pub fn py_repr(&self) -> String

The Python repr() of the value.

is_truthy

pub fn is_truthy(&self) -> bool

Whether the value is truthy under Python’s rules; see ObjectRef::is_truthy.

type_name

pub fn type_name(&self) -> &str

The Python type name of the value, e.g. "list".

Implements: Clone, Debug, Deserialize<'de>, Display, Eq, From<MontyObject>, PartialEq, PartialEq<MontyObject>, PartialEq<ObjectRef<'_>>, PushValue, Serialize, TryFrom<&MontyObject>.

MontyTime

pub struct MontyTime {
    /// Hour in range 0..=23.
    pub hour: u8,
    /// Minute in range 0..=59.
    pub minute: u8,
    /// Second in range 0..=59.
    pub second: u8,
    /// Microsecond in range 0..=999_999.
    pub microsecond: u32,
    /// Fixed offset seconds for aware times, or `None` for naive values.
    ///
    /// Within `MIN_TIMEZONE_OFFSET_SECONDS`..=`MAX_TIMEZONE_OFFSET_SECONDS` when set.
    pub offset_seconds: Option<i32>,
    /// Optional explicit timezone name for aware times.
    ///
    /// Must be `None` when `offset_seconds` is `None`.
    pub timezone_name: Option<String>,
    /// Fold flag, 0 or 1.
    pub fold: u8,
}

A Python datetime.time value: a wall clock with no date attached.

fold is carried so the flag survives the boundary, but neither monty nor this type interprets it — as in CPython it takes no part in equality.

Implements: Clone, Debug, Deserialize<'de>, Eq, Hash, PartialEq, Serialize.

MontyTimeDelta

pub struct MontyTimeDelta {
    /// Day component.
    pub days: i32,
    /// Seconds component in normalized range 0..86400.
    pub seconds: i32,
    /// Microseconds component in normalized range 0..1_000_000.
    pub microseconds: i32,
}

A Python datetime.timedelta value representing a duration.

Implements: Clone, Debug, Deserialize<'de>, Eq, Hash, PartialEq, Serialize, StructuralPartialEq.

MontyTimeZone

pub struct MontyTimeZone {
    /// Fixed UTC offset in seconds, within `MIN_TIMEZONE_OFFSET_SECONDS`..=`MAX_TIMEZONE_OFFSET_SECONDS`.
    pub offset_seconds: i32,
    /// Optional display name.
    pub name: Option<String>,
}

A Python datetime.timezone fixed-offset timezone.

Implements: Clone, Debug, Deserialize<'de>, Eq, Hash, PartialEq, Serialize.

MontyType

pub enum MontyType {
    Ellipsis,
    Type,
    NoneType,
    Bool,
    Int,
    Float,
    Range,
    Slice,
    /// The four `datetime` classes carry the qualified names the runtime
    /// `Type` uses (`datetime.date`, ...) rather than bare `date`, so a type
    /// object keeps one name either side of the boundary.
    Date,
    DateTime,
    TimeDelta,
    TimeZone,
    Str,
    Bytes,
    List,
    /// `collections.deque`. Qualified like `datetime.datetime` so the
    /// host-boundary name matches the runtime `Type::Deque` (`collections.deque`)
    /// rather than a bare `deque`.
    Deque,
    ListIterator,
    CallableIterator,
    Tuple,
    NamedTuple,
    Dict,
    DictKeys,
    DictItems,
    DictValues,
    Set,
    FrozenSet,
    /// Exception types render/parse via `ExcType`'s own strum name
    /// (`"ValueError"`, `"json.JSONDecodeError"`, ...), so this variant is
    /// `#[strum(disabled)]`: `name` and
    /// `from_type_name` peel `Exception` off
    /// explicitly.
    Exception(ExcType),
    Function,
    BuiltinFunction,
    Cell,
    Iterator,
    Coroutine,
    Module,
    TextIOWrapper,
    BufferedReader,
    BufferedWriter,
    BufferedRandom,
    SpecialForm,
    Path,
    Property,
    RePattern,
    ReMatch,
    TupleIterator,
    StrAsciiIterator,
    StrIterator,
    BytesIterator,
    RangeIterator,
    DictKeyIterator,
    DictItemIterator,
    DictValueIterator,
    SetIterator,
    ItertoolsCount,
    ItertoolsRepeat,
    /// A `dataclasses.Field` describing one field of a sandbox `@dataclass`,
    /// as found in a class's `__dataclass_fields__`.
    Field,
    ItertoolsPairwise,
    ItertoolsCompress,
    ItertoolsIslice,
    ItertoolsChain,
    ItertoolsCycle,
    NotImplementedType,
    /// The `__dataclass_params__` of a sandbox `@dataclass`: the options it was
    /// decorated with, named as CPython's private class reports itself.
    DataclassParams,
    ItertoolsTakeWhile,
    ItertoolsDropWhile,
    ItertoolsFilterFalse,
    ItertoolsStarMap,
    /// The builtin `object`, which the sandbox exposes as a name only — it is
    /// not a base class and cannot be constructed.
    Object,
    Time,
    /// `functools.partial`, qualified the way CPython's `tp_name` is.
    Partial,
    ItertoolsAccumulate,
    ItertoolsBatched,
    ItertoolsZipLongest,
    /// `types.GenericAlias`, the type of `list[int]`, qualified the way CPython's `tp_name` is.
    GenericAlias,
    /// `typing.Union`, the type of `int | None` (one object with `types.UnionType` since 3.14).
    Union,
    ItertoolsCombinations,
    ItertoolsCombinationsWithReplacement,
    ItertoolsPermutations,
    ItertoolsProduct,
    ItertoolsGroupBy,
    ItertoolsGrouper,
    ItertoolsTee,
    ItertoolsTeeDataObject,
}

The Python type of a builtin at the host boundary: the public mirror of the runtime Type enum, minus class types, which cross as their own class_type value. Serializable and displayable without heap access.

Every runtime type is mirrored, not only those a host materialises as its own class: the rest cross as a named proxy so the same type object can round-trip back into the sandbox.

name

pub fn name(&self) -> &str

The Python-visible name of this type ("int", "datetime.datetime", "ValueError").

from_type_name

pub fn from_type_name(name: &str) -> Option<Self>

Parses builtin and exception type names produced by Display/name. Unrecognized names return None; "object" parses to Object.

Implements: Clone, Copy, Debug, Deserialize<'de>, Display, Eq, From<&'_derivative_strum MontyType>, From<MontyType>, FromStr, IntoEnumIterator, PartialEq, Serialize, StructuralPartialEq, TryFrom<&str>, VariantNames.

NamedValues

pub struct NamedValues { /* private fields */ }

The named inputs of one feed, preserving sharing between exported values.

new

pub fn new() -> Self

No values.

push

pub fn push(&mut self, name: impl Into<String>, value: MontyObject)

Appends a named value.

len

pub fn len(&self) -> usize

Number of named values.

is_empty

pub fn is_empty(&self) -> bool

Whether there are no named values.

iter

pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, ObjectRef<'_>)>

The (name, value) pairs, in order.

Implements: Clone, Debug, Default, Deserialize<'de>, Eq, From<Vec<(String, MontyObject)>>, PartialEq, Serialize, StructuralPartialEq.

ObjectRef

pub struct ObjectRef<'a> { /* private fields */ }

A borrowed Python value, for inspecting results, arguments and inputs without copying.

type_name

pub fn type_name(&self) -> &'a str

The Python type name of the value, e.g. "list".

to_owned

pub fn to_owned(&self) -> MontyObject

Copies into an owned value, preserving sharing within the value.

items

pub fn items(&self) -> Option<Vec<Self>>

The items of a list, tuple, set, frozenset or namedtuple; None for any other value.

pairs

pub fn pairs(&self) -> Option<Vec<(Self, Self)>>

The (key, value) pairs of a dict, or the eager attrs of a class instance or class type object; None for any other value.

as_int

pub fn as_int(&self) -> Option<i64>

The value as an int, if it fits in 64 bits.

as_str

pub fn as_str(&self) -> Option<&'a str>

The value as a str.

as_bool

pub fn as_bool(&self) -> Option<bool>

The value as a bool.

as_float

pub fn as_float(&self) -> Option<f64>

The value as a float; an int converts as Python’s float() does.

py_repr

pub fn py_repr(&self) -> String

The Python repr() of the value.

Panics

Could panic if out of memory.

is_truthy

pub fn is_truthy(&self) -> bool

Whether the value is truthy under Python’s rules: None, False, zero and empty containers are falsy; everything else is truthy.

Implements: Clone, Copy, Debug, Display, PartialEq, PartialEq<MontyObject>, PartialEq<ObjectRef<'_>>, PushValue, TryFrom<ObjectRef<'_>>.

unstable

pub mod unstable;

Representation access for bindings and transport adapters.

These APIs expose the current storage of boundary values and carry no API compatibility guarantee: they may change or be removed in any release. Prefer MontyObject::as_ref and its value accessors when possible.

ClassTypeNode

pub struct ClassTypeNode {
    /// The Python-visible class name.
    pub name: String,
    /// Identity of the class, generated by whichever side defined it.
    pub id: MontyUuid,
    /// True for a host-defined class, false for a sandbox-defined one.
    pub host_defined: bool,
    /// Whether `dataclasses.is_dataclass` is true for the class.
    pub is_dataclass: bool,
    /// Eagerly-sent class attributes as `(name, value)` id pairs.
    pub attrs: Vec<(NodeId, NodeId)>,
}

Payload of MontyNode::ClassType: a class shared by every instance of it in the arena.

Implements: Clone, Debug, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

GraphError

pub enum GraphError {
    /// A node references a child at or above its own index.
    IndexNotLower { node: NodeId, child: NodeId },
    /// A class-instance node's `class_type` is not a class-type node.
    ClassTypeNotAClass { node: NodeId },
    /// A root id is outside the arena.
    RootOutOfRange { root: NodeId, len: usize },
}

Why a node sequence is not a valid MontyGraph.

Implements: Clone, Debug, Display, Eq, Error, PartialEq, StructuralPartialEq.

MontyGraph

pub struct MontyGraph { /* private fields */ }

A post-order node arena.

push and from_nodes check the invariants (see validate) and merge preserves them, so readers index without re-checking. Roots are held by the carrying message, so one arena serves every value in that message.

new

pub fn new() -> Self

An empty arena.

with_capacity

pub fn with_capacity(capacity: usize) -> Self

An empty arena with room for capacity nodes.

from_nodes

pub fn from_nodes(nodes: Vec<MontyNode>) -> Result<Self, GraphError>

Adopts already-built nodes after validating them.

validate

pub fn validate(nodes: &[MontyNode]) -> Result<(), GraphError>

Checks that child ids precede their holders and class-instance nodes reference class-type nodes.

push

pub fn push(&mut self, node: MontyNode) -> NodeId

Appends a node, returning its id.

Panics

If the node violates the arena invariants; children must be pushed before the node that holds them.

merge

pub fn merge(&mut self, other: Self) -> u32

Appends every node of other, rebasing its ids, and returns the offset added to them so the caller can rebase its own roots.

len

pub fn len(&self) -> usize

Number of nodes.

is_empty

pub fn is_empty(&self) -> bool

Whether the arena has no nodes.

node

pub fn node(&self, id: NodeId) -> &MontyNode

The node at id.

Panics

If id is out of range; ids come from this arena, so that is a bug.

node_mut

pub fn node_mut(&mut self, id: NodeId) -> &mut MontyNode

Mutable access to the node at id. Callers must keep the invariants: replacing a node’s child ids with lower ones is fine, raising them is not.

Panics

If id is out of range.

nodes_mut

pub fn nodes_mut(&mut self) -> &mut [MontyNode]

Every node, in post-order, for editing payloads in place; reordering nodes or raising a child id breaks the arena’s invariants.

nodes

pub fn nodes(&self) -> &[MontyNode]

Every node, in post-order.

into_nodes

pub fn into_nodes(self) -> Vec<MontyNode>

Takes the nodes out of the arena.

check_root

pub fn check_root(&self, root: NodeId) -> Result<(), GraphError>

Checks that root (an id received alongside this arena) is in range.

type_name

pub fn type_name(&self, id: NodeId) -> &str

The Python type name of the value at id, e.g. "list"; a class instance reports its class name.

decoded_size

pub fn decoded_size(&self) -> usize

Sum of MontyNode::decoded_size over every node: the arena’s decoded footprint, used by transport budgets.

value

pub fn value(&self, id: NodeId) -> ObjectRef<'_>

Borrows the value rooted at id.

Panics

If id is out of range; ids come from this arena, so that is a bug.

Implements: Clone, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

MontyNode

pub enum MontyNode {
    /// Python's `Ellipsis` singleton (`...`).
    Ellipsis,
    /// Python's `NotImplemented` singleton.
    NotImplemented,
    /// Python's `None` singleton.
    None,
    /// Python boolean.
    Bool(bool),
    /// Python integer fitting in 64 bits.
    Int(i64),
    /// Python integer wider than 64 bits.
    BigInt(num_bigint::BigInt),
    /// Python float.
    Float(f64),
    /// Python string.
    String(String),
    /// Python bytes.
    Bytes(Vec<u8>),
    /// Python `datetime.date`.
    Date(MontyDate),
    /// Python `datetime.datetime`.
    DateTime(MontyDateTime),
    /// Python `datetime.time`.
    Time(MontyTime),
    /// Python `datetime.timedelta`.
    TimeDelta(MontyTimeDelta),
    /// Python `datetime.timezone`.
    TimeZone(MontyTimeZone),
    /// A Python exception value with type and optional message.
    Exception { exc_type: ExcType, arg: Option<String> },
    /// A builtin type object; a non-builtin class is a `ClassType` node.
    Type(MontyType),
    /// A builtin function such as `len`.
    BuiltinFunction(BuiltinsFunctions),
    /// A `pathlib.Path` (always a virtual POSIX path).
    Path(String),
    /// An open file object.
    FileHandle(MontyFileHandle),
    /// An external function provided by the host.
    Function { name: String, docstring: Option<String> },
    /// Output-only fallback: the `repr()` of a value with no other representation.
    Repr(String),
    /// Output-only: a reference back to a container that encloses this node,
    /// as the placeholder its repr shows (`[...]`, `(...)`, `{...}` or `...`).
    Cycle(String),
    /// Python list: ids of its items.
    List(Vec<NodeId>),
    /// Python tuple: ids of its items.
    Tuple(Vec<NodeId>),
    /// Python set: ids of its elements.
    Set(Vec<NodeId>),
    /// Python frozenset: ids of its elements.
    FrozenSet(Vec<NodeId>),
    /// Python named tuple: field names and the ids of the values.
    NamedTuple { type_name: String, field_names: Vec<String>, values: Vec<NodeId> },
    /// Python dict: `(key, value)` id pairs in insertion order.
    Dict(Vec<(NodeId, NodeId)>),
    /// A sandbox- or host-defined class, shared by every instance of it.
    /// Boxed so the variant does not widen the node.
    ClassType(Box<ClassTypeNode>),
    /// An instance of a non-builtin class.
    ClassInstance { class_type: NodeId, instance_id: MontyUuid, attrs: Vec<(NodeId, NodeId)> },
}

One entry of a MontyGraph: a leaf value, or a container holding the ids of its children.

A non-builtin class is its own ClassType node, shared by every instance of it. Build values with the MontyObject constructors rather than from nodes.

for_each_child

pub fn for_each_child(&self, f: impl FnMut(NodeId))

Calls f with every child id this node holds, in order; for ClassInstance the class id comes first.

for_each_child_mut

pub fn for_each_child_mut(&mut self, f: impl FnMut(&mut NodeId))

Mutable counterpart of for_each_child, used to rebase ids when arenas are merged.

cycle_placeholder

pub fn cycle_placeholder(&self) -> &'static str

The placeholder a reference back to this node renders as, matching CPython’s recursive repr() markers.

is_leaf

pub fn is_leaf(&self) -> bool

Whether the node holds no child ids.

metadata_string_size

pub const fn metadata_string_size(value: &str) -> usize

The host footprint of one owned string in a metadata vector, such as a namedtuple’s field names.

decoded_size

pub fn decoded_size(&self) -> usize

Host footprint of this node once decoded: the fixed enum size plus the bytes it owns directly (string, bytes and bigint payloads, field names, and its child-id vectors). Children charge themselves, so an arena’s footprint is the plain sum over its nodes.

Implements: Clone, Debug, Deserialize<'de>, Eq, PartialEq, PushValue, Serialize.

NodeId

pub struct NodeId(pub u32);

Index of a node in a MontyGraph.

Only meaningful together with the arena it was issued by: ids are dense positions, not identities, and MontyGraph::merge rebases them.

index

pub fn index(self) -> usize

The id as a vector index.

Implements: Clone, Copy, Debug, Deserialize<'de>, Display, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, StructuralPartialEq.

CallArgsParts

pub type CallArgsParts = (MontyGraph, Vec<NodeId>, Vec<(NodeId, NodeId)>);

The owned arena, positional roots and keyword roots of a call.

CallArgsPartsRef

pub type CallArgsPartsRef<'a> = (&'a MontyGraph, &'a [NodeId], &'a [(NodeId, NodeId)]);

Borrowed call storage, without copying nodes or roots.

CallArgsPartsMut

pub type CallArgsPartsMut<'a> = (&'a mut MontyGraph, &'a mut Vec<NodeId>, &'a mut Vec<(NodeId, NodeId)>);

Mutable call storage for graph-level construction.

graph_parts

pub fn graph_parts(value: &MontyObject) -> (&MontyGraph, NodeId);

Borrows the current arena and its root without copying. This exposes storage rather than a stable value interface.

use monty_types::{MontyObject, unstable};
let value = MontyObject::int(42);
let (graph, root) = unstable::graph_parts(&value);
assert_eq!(graph.value(root), value.as_ref());

into_graph_parts

pub fn into_graph_parts(value: MontyObject) -> (MontyGraph, NodeId);

Takes the current arena and root without copying. Rebuild an edited graph with object_from_graph to check the root pairing.

object_from_graph

pub fn object_from_graph(
    graph: MontyGraph,
    root: NodeId,
) -> Result<MontyObject, GraphError>;

Pairs an arena with a root, checking the root is in range.

object_from_node

pub fn object_from_node(node: MontyNode) -> MontyObject;

Builds a value from a single node.

Panics

If the node holds child ids or otherwise fails graph validation.

root_node

pub fn root_node(value: &MontyObject) -> &MontyNode;

Borrows the stored root node, exposing its arena-relative child ids.

node

pub fn node(value: ObjectRef<'_>) -> &MontyNode;

Borrows the stored node of a value view, exposing its arena-relative child ids.

child

pub fn child(value: ObjectRef<'_>, id: NodeId) -> ObjectRef<'_>;

Borrows another node from the view’s arena.

Panics

If id is out of range; ids must come from this arena.

call_args_parts

pub fn call_args_parts(args: &CallArgs) -> CallArgsPartsRef<'_>;

Borrows the arena and roots of a call without copying.

call_args_parts_mut

pub fn call_args_parts_mut(args: &mut CallArgs) -> CallArgsPartsMut<'_>;

Borrows call storage for graph-level construction. Restore valid roots before inspecting or transmitting the arguments.

into_call_args_parts

pub fn into_call_args_parts(args: CallArgs) -> CallArgsParts;

Takes the arena and roots of a call without copying.

call_args_from_parts

pub fn call_args_from_parts(
    graph: MontyGraph,
    arg_ids: Vec<NodeId>,
    kwarg_ids: Vec<(NodeId, NodeId)>,
) -> Result<CallArgs, GraphError>;

Builds a call from graph storage, rejecting out-of-range argument roots.

named_values_parts

pub fn named_values_parts(values: &NamedValues) -> (&MontyGraph, &[(String, NodeId)]);

Borrows the arena and named roots of a feed without copying.

into_named_values_parts

pub fn into_named_values_parts(values: NamedValues) -> (MontyGraph, Vec<(String, NodeId)>);

Takes the arena and named roots of a feed without copying.

named_values_from_parts

pub fn named_values_from_parts(
    graph: MontyGraph,
    names: Vec<(String, NodeId)>,
) -> Result<NamedValues, GraphError>;

Builds feed inputs from graph storage, rejecting out-of-range value roots.

push_arg

pub fn push_arg(args: &mut CallArgs, value: impl PushValue) -> NodeId;

Appends an argument directly to the call’s arena and returns its node id.

push_kwarg

pub fn push_kwarg(args: &mut CallArgs, name: &str, value: impl PushValue);

Appends a keyword argument directly to the call’s arena.

push_named

pub fn push_named(
    values: &mut NamedValues,
    name: impl Into<String>,
    value: impl PushValue,
) -> NodeId;

Appends a named value directly to the feed’s arena and returns its node id.

PushValue

pub trait PushValue {
    fn push_into(self, graph: &mut MontyGraph) -> NodeId;
}

Consumes self into a node of graph, returning its id. Composite values push their children first so the arena stays post-order.

push_into

Appends a value with its children below it, returning its arena-relative id.

GetenvArgs

pub struct GetenvArgs {
    pub key: String,
    pub default: MontyObject,
}

os.getenv(key, default=None) shape. The host decides whether to substitute default when the variable is unset.

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

MAX_SLEEP_SECONDS

pub const MAX_SLEEP_SECONDS: f64 = 9_223_372_036.854_775;

Longest sleep the sleep calls accept, matching the point where CPython’s PyTime_t (nanoseconds in an i64) overflows.

MkdirCallArgs

pub struct MkdirCallArgs {
    pub path: MontyPath,
    pub parents: bool,
    pub exist_ok: bool,
}

mkdir(path, parents=False, exist_ok=False) shape. parents/exist_ok are kw-only so ToArgs emits them as kwargs (matching CPython).

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

MontyPath

pub struct MontyPath(/* private */);

Owned virtual (sandbox) path carried by OS-call args.

Preserves the supplied string, including invalid components, for host validation. Derefs to &str for routing.

new

pub fn new(path: String) -> Self

Stores the path without validation or normalization; hosts validate before I/O.

as_str

pub fn as_str(&self) -> &str

Borrows the original spelling for host validation and error messages.

into_string

pub fn into_string(self) -> String

Takes the original string without copying it.

Implements: Clone, Debug, Deref, Deserialize<'de>, Eq, From<&str>, From<String>, PartialEq, PushValue, Serialize, StructuralPartialEq.

OpenCallArgs

pub struct OpenCallArgs {
    pub path: MontyPath,
    pub mode: FileMode,
}

Arguments to open(): a virtual path and a parsed file mode.

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

OsFunctionCall

pub enum OsFunctionCall {
    /// Check if a path exists.
    Exists(MontyPath),
    /// Check if path is a regular file.
    IsFile(MontyPath),
    /// Check if path is a directory.
    IsDir(MontyPath),
    /// Check if path is a symbolic link.
    IsSymlink(MontyPath),
    /// Read file contents as text.
    ReadText(MontyPath),
    /// Read file contents as bytes.
    ReadBytes(MontyPath),
    /// `stat()` — return a stat result tuple.
    Stat(MontyPath),
    /// List directory contents.
    Iterdir(MontyPath),
    /// Resolve symlinks and return absolute path.
    Resolve(MontyPath),
    /// Absolute path without symlink resolution.
    Absolute(MontyPath),
    /// Write text to file (truncating).
    WriteText(PathStringDataArgs),
    /// Append text to file.
    AppendText(PathStringDataArgs),
    /// Write bytes to file (truncating).
    WriteBytes(PathBytesDataArgs),
    /// Append bytes to file.
    AppendBytes(PathBytesDataArgs),
    /// Open a file. The host performs the open-time effect (truncate for
    /// `w`/`w+`, create-if-missing for `a`/`a+`, existence check for `r`/`r+`)
    /// and returns a `MontyFileHandle` — it never holds a live OS
    /// handle across calls.
    Open(OpenCallArgs),
    /// Create directory (`parents`/`exist_ok` kwargs).
    Mkdir(MkdirCallArgs),
    /// Remove file.
    Unlink(MontyPath),
    /// Remove directory.
    Rmdir(MontyPath),
    /// Rename / move (src → dst).
    Rename(RenameCallArgs),
    /// Get an environment variable value.
    Getenv(GetenvArgs),
    /// Get the entire environment as a dictionary.
    GetEnviron,
    /// Get today's date from the host system (for `date.today()`).
    DateToday,
    /// Get the current date/time from the host system (for `datetime.now(tz=...)`).
    /// Carries the timezone argument, `None` for a naive result.
    DateTimeNow(Option<MontyTimeZone>),
    /// Read `size` bytes of entropy from the host (for `os.urandom(size)`, and
    /// how the `random` module seeds an unseeded generator).
    Urandom(UrandomArgs),
    /// Read the host clock as `time.time()` does: seconds since the Unix
    /// epoch, answered with `MontyObject::float`. Every clock-reading `time`
    /// function arrives here; `TimeCaller`, the call's only argument, says which.
    Time(TimeCaller),
    /// `time.sleep(seconds)` under `SleepMode::CallHost` — the host's `os`
    /// handler waits, then answers with any value (`time.sleep` discards it
    /// and evaluates to `None`).
    Sleep(std::time::Duration),
    /// `time.sleep(seconds)` under `SleepMode::System`, capped at its maximum.
    /// The host charges `max_total_sleep`, waits and returns `None` without its
    /// `os` handler. The distinct name identifies the policy for the host.
    SystemSleep(std::time::Duration),
    /// `asyncio.sleep(delay)` — like `Sleep`, except the
    /// sandbox turns the answer into an awaitable, so a host that runs an
    /// event loop should answer with a future (`ExtFunctionResult::Future`)
    /// and resolve it when the delay elapses, letting sibling tasks run
    /// meanwhile. The answer's value is ignored: the sandbox keeps the
    /// `result` argument itself and produces it from the `await`.
    AsyncSleep(std::time::Duration),
    /// `asyncio.sleep(delay)` under `SleepMode::System`: the awaitable form of
    /// `SystemSleep`, which a host running an event loop
    /// answers with a future it resolves once the delay elapses.
    AsyncSystemSleep(std::time::Duration),
}

Tagged dispatch value for OS-level operations.

Each variant carries the strongly-typed args/kwargs the corresponding OS call needs. The fs/ layer matches on this enum directly (no value introspection); host bindings get a generic (positional, keyword) view via OsFunctionCall::to_args.

See the module docs for how to add a new variant.

accepts_future

pub fn accepts_future(name: &str) -> bool

Whether this name accepts ExtFunctionResult::Future, letting other tasks run until the host resolves it. Only asyncio.sleep qualifies, in either sleep mode; all other calls require an immediate answer.

name

pub fn name(&self) -> &'static str

Stable string name for this OS function — surfaces in Self::on_no_handler errors, host os callbacks, and serialised snapshots. The strum serialize string on each variant.

to_args

pub fn to_args(self) -> CallArgs

Projects this call’s args into the CallArgs delivered to a host callback, with lexically normalized paths. Empty paths stay empty. The interpreter checks NUL bytes before dispatch; hosts constructing calls must use Self::check_path_null_bytes first. Mounts must validate length limits on the original typed call.

is_write

pub fn is_write(&self) -> bool

Whether this call mutates filesystem state — the read-only-mount gate. Open’s write-ness is mode-dependent (w/w+/a/a+ write; r/r+ don’t).

is_existence_check

pub fn is_existence_check(&self) -> bool

Whether this operation checks existence without reading content. Existence checks return false for nonexistent paths rather than raising FileNotFoundError, matching CPython’s pathlib.Path.

check_path_null_bytes

pub fn check_path_null_bytes(&self) -> Result<(), &'static str>

Checks both raw filesystem paths before normalization can hide a NUL byte. Returns the operation-specific ValueError message; existence predicates should return False instead of raising it.

embedded_null_message

pub fn embedded_null_message(&self, for_destination: bool) -> &'static str

CPython’s ValueError message for a path containing a null byte.

The wording is not uniform in CPython: it comes from whichever layer first inspects the path, so the content operations go through open() and say embedded null byte, while the metadata ones are named by the syscall their os wrapper was about to make. for_destination picks the rename argument that carried the byte.

fs_primary_path

pub fn fs_primary_path(&self) -> Option<&str>

The call’s primary path if it’s a FS operation, None otherwise.

Used for routing and error reporting.

rename_destination

pub fn rename_destination(&self) -> Option<&str>

The rename destination path, or None for every other variant — the second routing key a mount table needs (both rename endpoints must resolve to the same mount).

fs_paths_mut

pub fn fs_paths_mut(&mut self) -> impl Iterator<Item = &mut MontyPath>

Every path this call carries, mutably: the primary path plus the rename destination. The interpreter resolves relative paths against the sandbox working directory here before the call reaches the host, so host backends only ever see absolute virtual paths.

on_no_handler

pub fn on_no_handler(&self) -> MontyException

Exception to raise when no handler accepted this call: PermissionError for FS ops (with the path), RuntimeError for non-FS ops.

Implements: Clone, Debug, Deserialize<'de>, Display, From<&'_derivative_strum OsFunctionCall>, From<OsFunctionCall>, Serialize.

PathBytesDataArgs

pub struct PathBytesDataArgs {
    pub path: MontyPath,
    pub data: Vec<u8>,
}

path + bytes data shape used by WriteBytes and AppendBytes.

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

PathStringDataArgs

pub struct PathStringDataArgs {
    pub path: MontyPath,
    pub data: String,
}

path + str data shape used by WriteText and AppendText.

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

RenameCallArgs

pub struct RenameCallArgs {
    pub src: MontyPath,
    pub dst: MontyPath,
}

rename(src, dst) shape.

Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.

SleepError

pub enum SleepError {
    /// The delay was NaN.
    NotANumber,
    /// The delay was negative.
    Negative,
    /// The delay was past `MAX_SLEEP_SECONDS` (infinity included).
    TooLarge,
}

Why a requested sleep length cannot be carried by an OS call.

The caller picks the Python-level consequence: time.sleep raises (ValueError for the first two, OverflowError for the third) while asyncio.sleep raises only for NaN and clamps the rest, as CPython does.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq.

TimeCaller

pub enum TimeCaller {
    /// `time.time()`.
    Time,
    /// `time.time_ns()`.
    TimeNs,
    /// `time.monotonic()`.
    Monotonic,
    /// `time.monotonic_ns()`.
    MonotonicNs,
    /// `time.perf_counter()`.
    PerfCounter,
    /// `time.perf_counter_ns()`.
    PerfCounterNs,
    /// `time.gmtime()` with no argument.
    Gmtime,
    /// `time.localtime()` with no argument.
    Localtime,
    /// `time.asctime()` with no argument.
    Asctime,
    /// `time.ctime()` with no argument.
    Ctime,
    /// `time.strftime(format)` with no time argument.
    Strftime,
}

Which time function is reading the clock in an OsFunctionCall::Time.

All share the time.time call name since all want the current instant as epoch seconds; the caller, passed as the call’s single positional argument spelled as the Python function ("time.perf_counter"), lets a host that cares answer them differently (say a virtual clock advancing only for time.monotonic).

as_str

pub fn as_str(self) -> &'static str

The Python function’s name, as the host receives it.

Implements: Clone, Copy, Debug, Deserialize<'de>, Display, Eq, From<&'_derivative_strum TimeCaller>, From<TimeCaller>, FromStr, IntoEnumIterator, PartialEq, PushValue, Serialize, StructuralPartialEq, TryFrom<&str>.

UrandomArgs

pub struct UrandomArgs {
    pub size: u64,
}

os.urandom(size) shape. The interpreter rejects a negative size before suspending, so the count is unsigned; the host answers with exactly size bytes. size is sandbox-controlled, so a handler should cap it before allocating.

Implements: Clone, Debug, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq, ToArgs.

dir_stat

pub fn dir_stat(mode: i64, mtime: f64) -> MontyObject;

Creates a stat_result for a directory.

The directory type bits (0o040_000) are automatically added if not present.

Arguments

  • mode - Directory permissions as octal. Common values:
    • 0o755 - rwxr-xr-x (owner full, others read/execute)
    • 0o700 - rwx------ (owner only)
    • 0o040755 - same as 0o755 with explicit directory type bits
  • mtime - Modification time as Unix timestamp

file_stat

pub fn file_stat(mode: i64, size: i64, mtime: f64) -> MontyObject;

Creates a stat_result for a regular file.

The file type bits (0o100_000) are automatically added if not present.

Arguments

  • mode - File permissions as octal. Common values:
    • 0o644 - rw-r—r— (owner read/write, others read)
    • 0o600 - rw------- (owner read/write only)
    • 0o755 - rwxr-xr-x (executable, owner full, others read/execute)
    • 0o100644 - same as 0o644 with explicit file type bits
  • size - File size in bytes
  • mtime - Modification time as Unix timestamp

sleep_duration

pub fn sleep_duration(seconds: f64) -> Result<std::time::Duration, SleepError>;

Converts a Python sleep argument into the Duration an OS call carries.

Sleep payloads are Duration rather than raw seconds precisely so no host is ever handed a NaN, negative or unrepresentable span to convert — the obvious Duration::from_secs_f64 panics on all three. Both producers, the interpreter and the wire decoder, go through here.

sleep_duration_saturating

pub fn sleep_duration_saturating(seconds: f64) -> Result<std::time::Duration, SleepError>;

Like sleep_duration, but for asyncio.sleep, which clamps rather than raising: a negative delay becomes no wait at all (CPython returns immediately) and an over-long one saturates at MAX_SLEEP_SECONDS. Only NaN is refused, the one delay CPython rejects there.

stat_result

pub fn stat_result(
    st_mode: i64,
    st_ino: i64,
    st_dev: i64,
    st_nlink: i64,
    st_uid: i64,
    st_gid: i64,
    st_size: i64,
    st_atime: f64,
    st_mtime: f64,
    st_ctime: f64,
) -> MontyObject;

Creates a full stat_result with all 10 fields specified.

This is the low-level builder; prefer file_stat(), dir_stat(), or symlink_stat() for common cases.

pub fn symlink_stat(mode: i64, mtime: f64) -> MontyObject;

Creates a stat_result for a symbolic link.

The symlink type bits (0o120_000) are automatically added if not present.

Arguments

  • mode - Symlink permissions as octal. Common values:
    • 0o777 - rwxrwxrwx (symlinks typically have full permissions)
    • 0o120777 - same as 0o777 with explicit symlink type bits
  • mtime - Modification time as Unix timestamp

DateTimeSource

pub enum DateTimeSource {
    /// The process's own clock.
    System,
    /// Suspend to the host, which answers each call.
    CallHost,
    /// Every call reads this instant. Years outside 1..=9999 raise `OverflowError`.
    Fixed { unix_seconds: i64, microsecond: u32 },
}

Where the clock calls read the instant.

read

pub fn read(self) -> Option<NaiveDateTime>

Reads the instant in UTC. Returns None for CallHost or a Fixed instant Python’s datetime cannot represent.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

NamedZone

pub struct NamedZone { /* private fields */ }

A zone resolved from the tz database by SandboxTimeZone::named, which is the only way to build one: every value carries the database’s name for it.

name

pub fn name(&self) -> &str

The zone’s IANA name as the database spells it.

Implements: Clone, Debug, Deserialize<'de>, Eq, From<NamedZone>, PartialEq, Serialize, StructuralPartialEq, TryFrom<String>.

OsPolicy

pub struct OsPolicy {
    /// The instant `date.today()`, `datetime.now()` and `time.time()` read.
    pub datetime: DateTimeSource,
    /// The local zone: naive `datetime.now()` and `date.today()` read it,
    /// `astimezone()`, `time.timezone`/`tzname` and `%Z` report it.
    pub timezone: SandboxTimeZone,
    /// What `time.sleep()` and `asyncio.sleep()` do.
    pub sleep: SleepMode,
    /// What `time.process_time()` and `time.thread_time()` report.
    pub process_time: ProcessTime,
    /// Where an unseeded `random` generator gets its first state.
    pub random_start: RandomStart,
}

Policies for clocks, sleeps and initial random state on every execution path. CallHost suspends to the host, or raises NotImplementedError without one; the zone is always resolved in the sandbox. Defaults use the system clock, the UTC zone, OS entropy, a zero process clock, and sleeps capped at ten seconds. Hosts perform those sleeps without their os handler; standard execution waits inline.

Implements: Clone, Debug, Default, Deserialize<'de>, PartialEq, Serialize, StructuralPartialEq.

ProcessTime

pub enum ProcessTime {
    /// Always zero, so a `DateTimeSource::Fixed` session stays blind to time passing.
    Zero,
    /// The session's accumulated execution time, as `ResourceTracker::elapsed`
    /// measures it. Wall time while running, not CPU time.
    Elapsed,
}

What time.process_time() and time.thread_time() report.

Separate from DateTimeSource because these clocks exclude sleeps and time suspended on the host, which no wall clock can express.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

RandomSeed

pub enum RandomSeed {
    /// Any size; CPython seeds from the absolute value.
    Int(num_bigint::BigInt),
    /// Seeded from the float's CPython `hash()`.
    Float(f64),
    /// Seeded from the SHA-512-extended text, as `seed(str)` does.
    Str(String),
    /// Seeded from the SHA-512-extended bytes, as `seed(bytes)` does.
    Bytes(Vec<u8>),
}

Explicit seeds for RandomStart::Seed, using CPython’s random.seed() semantics.

Implements: Clone, Debug, Deserialize<'de>, PartialEq, Serialize, StructuralPartialEq.

RandomStart

pub enum RandomStart {
    /// From the sandbox's own OS entropy.
    System,
    /// Request one state vector (2496 bytes) from the host via `os.urandom` on the first draw.
    CallHost,
    /// The module-level generator starts exactly as `random.seed(seed)` leaves
    /// it; unseeded `random.Random()` instances take deterministic states
    /// derived from the same seed.
    Seed(RandomSeed),
}

Where an unseeded random generator gets its first state.

Implements: Clone, Debug, Default, Deserialize<'de>, PartialEq, Serialize, StructuralPartialEq.

SandboxTimeZone

pub enum SandboxTimeZone {
    /// A fixed offset from UTC, with the name `datetime.timezone(offset, name)`
    /// would carry. No DST: every instant has the same offset and name.
    Fixed { offset_seconds: i32, name: Option<String> },
    /// An IANA zone with its transition rules, built only by `named`.
    /// Serialises as its name, so each side of the wire resolves it against its
    /// own database.
    Named(NamedZone),
}

The sandbox’s local zone, UTC unless configured.

utc

pub fn utc() -> Self

UTC named UTC, as CPython reports the zone under TZ=UTC.

named

pub fn named(name: &str) -> Result<Self, UnknownTimeZone>

Resolves an IANA zone name such as Europe/London against the tz database the tzdb or tzdb-bundled feature provides. Without either, or for a name the database lacks, returns UnknownTimeZone.

iana_name

pub fn iana_name(&self) -> Option<&str>

The IANA name of a Named zone; None for a fixed offset.

at

pub fn at(&self, utc: NaiveDateTime) -> MontyTimeZone

The offset and name in force at the UTC instant utc: the timezone that astimezone() attaches and %Z prints. A fixed zone’s name is None when it was not given one.

offset_for_local

pub fn offset_for_local(&self, local: NaiveDateTime) -> Option<i32>

The offset in force for a naive wall-clock time in this zone. An ambiguous time (a DST fold) takes its first occurrence and a skipped time (a gap) the offset from before it, CPython’s fold=0 reading. None only outside the civil year range, which is wider than datetime’s.

utc_from_local

pub fn utc_from_local(&self, local: NaiveDateTime) -> Option<NaiveDateTime>

The UTC instant a naive wall-clock time in this zone denotes, or None outside years 1..=9999.

constants

pub fn constants(&self, year: Option<i32>) -> Option<ZoneConstants>

The time module’s timezone, altzone, daylight and tzname, read as CPython does from the zone’s state on 1 January and 1 July of year. A fixed zone ignores year; a named one needs it, so None gives None.

Implements: Clone, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

SleepMode

pub enum SleepMode {
    /// Cap each delay at this duration, then suspend to the host to wait without
    /// its `os` handler. The host charges `ResourceLimits::max_total_sleep` before
    /// waiting; waits do not count as execution time. Standard execution waits
    /// inline without a cumulative limit. Longer delays are capped, not rejected.
    System(std::time::Duration),
    /// Delegate to the host's `os` handler without capping or charging the delay.
    CallHost,
    /// Return at once without waiting.
    Zero,
}

What the sleep calls do.

DEFAULT_MAX

pub const DEFAULT_MAX: Duration = _;

Default per-call cap for System.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

UnknownTimeZone

pub struct UnknownTimeZone(pub String);

A zone name SandboxTimeZone::named could not resolve: malformed, or absent from the tz database available to this build.

Implements: Clone, Debug, Display, Eq, Error, PartialEq, StructuralPartialEq.

ZoneConstants

pub struct ZoneConstants {
    /// Standard time: `-offset_seconds` is `time.timezone`, `name` is `tzname[0]`.
    pub standard: MontyTimeZone,
    /// Daylight time, equal to `standard` when the zone has none: `time.altzone`, `tzname[1]`.
    pub daylight_zone: MontyTimeZone,
    /// `time.daylight`: whether the two halves differ.
    pub daylight: bool,
}

The zone’s standard and daylight halves as the time module reports them.

Implements: Clone, Debug, Eq, PartialEq, StructuralPartialEq.

local_wall_clock

pub fn local_wall_clock(
    utc: chrono::NaiveDateTime,
    offset_seconds: i32,
) -> Option<chrono::NaiveDateTime>;

The wall clock offset_seconds from UTC at utc, or None outside the 1..=9999 years Python’s datetime can hold.

unix_seconds

pub fn unix_seconds(utc: chrono::NaiveDateTime) -> f64;

The instant as time.time() reports it: seconds since the Unix epoch.

BASELINE_MEMORY

pub static BASELINE_MEMORY: std::sync::atomic::AtomicUsize;

The leanest the process has ever been at an arming point: what the worker costs to exist, before any session ran.

DEFAULT_MAX_RECURSION_DEPTH

pub const DEFAULT_MAX_RECURSION_DEPTH: usize = 1000;

Recommended maximum recursion depth if not otherwise specified.

DEFAULT_MAX_SUSPENSIONS

pub const DEFAULT_MAX_SUSPENSIONS: usize = 1000;

Maximum suspensions a host services per session if not otherwise specified: a backstop against a sandbox looping on host calls while the execution clock is paused.

LARGE_RESULT_THRESHOLD

pub const LARGE_RESULT_THRESHOLD: usize = 100_000;

Threshold in bytes above which check_large_result is called.

Operations that may produce results larger than this threshold (100KB) should call check_large_result before performing the operation. This prevents DoS attacks where operations like 2 ** 10_000_000 allocate huge amounts of memory before the memory check can catch them.

LIVE_MEMORY

pub static LIVE_MEMORY: std::sync::atomic::AtomicUsize;

Allocator-backed live bytes requested through the global allocator

OOM_EXIT_CODE

pub const OOM_EXIT_CODE: i32 = 65;

Exit code a worker uses when it exceeded its memory limit or the allocator refused an allocation, so the parent can report MemoryError instead of an unclassifiable SIGABRT.

EX_DATAERR from BSD sysexits.h. See https://man.freebsd.org/cgi/man.cgi?query=sysexits.

ResourceError

pub enum ResourceError {
    /// One of the two execution-time budgets was exceeded; `scope` says which.
    Time { scope: TimeLimitScope, limit: std::time::Duration, elapsed: std::time::Duration },
    /// Maximum memory usage exceeded.
    Memory { limit: usize, used: usize },
    /// Maximum recursion depth exceeded.
    Recursion { limit: usize, depth: usize },
}

Error returned when a resource limit is exceeded during execution.

This allows the sandbox to enforce strict limits on execution time and memory usage.

All variants except Recursion are uncatchable inside the sandbox: untrusted code must never intercept resource enforcement. Recursion surfaces as a catchable RecursionError, matching CPython.

Implements: Clone, Debug, Display, Error, From<ResourceError>.

ResourceLimits

pub struct ResourceLimits {
    /// Maximum execution time for a single feed (`feed_start`, `feed_run` or
    /// `call_function`), summed over the turns it takes and excluding time
    /// suspended on the host. Bounds one snippet, not the session.
    pub max_feed_duration: Option<std::time::Duration>,
    /// Maximum execution time for a single host turn, reset at each feed and
    /// each resume. Bounds the stretch of sandbox code between two host round
    /// trips, so a host can bound its own response time per call.
    pub max_turn_duration: Option<std::time::Duration>,
    /// Maximum allocator-backed memory in bytes.
    ///
    /// Requires the executable to install and arm `monty-alloc`.
    pub max_memory: Option<usize>,
    /// Run garbage collection every N GC-tracked allocations.
    pub gc_interval: Option<usize>,
    /// Maximum recursion depth (function call stack depth).
    pub max_recursion_depth: usize,
    /// Maximum suspensions the host may service (default
    /// `DEFAULT_MAX_SUSPENSIONS`; always bounded, like recursion depth).
    /// The interpreter only stores this limit; hosts must enforce it.
    pub max_suspensions: usize,
    /// Maximum cumulative sleep under `SleepMode::System`, enforced by the host.
    /// Sleeps do not count toward execution duration; without this limit, sleeping
    /// loops need `max_suspensions` or a host deadline.
    pub max_total_sleep: Option<std::time::Duration>,
}

Configuration for resource limits.

The time/memory/GC limits are optional — set to None to disable — but recursion depth and the suspension budget are always bounded (defaults DEFAULT_MAX_RECURSION_DEPTH and DEFAULT_MAX_SUSPENSIONS): unbounded recursion would let sandboxed code overflow the native stack and abort the process, and unbounded suspensions would let it loop on host calls. Use ResourceLimits::default() for the recursion-only defaults, or build custom limits with the builder pattern.

max_feed_duration

pub fn max_feed_duration(self, limit: Duration) -> Self

Sets the maximum execution duration for any single feed.

max_turn_duration

pub fn max_turn_duration(self, limit: Duration) -> Self

Sets the maximum execution duration for any single host turn.

max_memory

pub fn max_memory(self, limit: usize) -> Self

Sets allocator-backed maximum memory usage in bytes.

Requires the executable to install and arm monty-alloc; otherwise the limit is silently not enforced.

gc_interval

pub fn gc_interval(self, interval: usize) -> Self

Sets the garbage collection interval (run GC every N GC-tracked allocations).

max_recursion_depth

pub fn max_recursion_depth(self, limit: usize) -> Self

Sets the maximum recursion depth (function call stack depth).

max_suspensions

pub fn max_suspensions(self, limit: usize) -> Self

Sets the host-enforced maximum number of suspensions.

max_total_sleep

pub fn max_total_sleep(self, limit: Duration) -> Self

Sets the maximum cumulative time hosts wait on system sleeps; each capped delay is charged before the wait.

Implements: Clone, Debug, Default, Deserialize<'de>, Serialize.

ResourceTracker

pub struct ResourceTracker { /* private fields */ }

A resource tracker that enforces configurable limits.

Checks allocator-backed memory usage and tracks execution time, returning errors when limits are exceeded. It also schedules garbage collection.

Uses Cell for mutable timing and recursion state behind shared references.

The two duration limits share one execution time clock: it runs only between the outermost on_execution_start/on_execution_stop pair, so it is paused while suspended on the host and between REPL feeds. They differ only in when their accumulator resets — at on_feed_start, at on_turn_start — and the feed total is serialized, so a session loaded mid-feed resumes that budget rather than restarting from zero.

new

pub fn new(limits: ResourceLimits) -> Self

Creates a new ResourceTracker with the given limits.

The execution-time clock starts at zero and only runs while the VM executes, so the tracker can be created any amount of time before the first run without consuming a duration budget. A configured max_memory requires monty-alloc installed as the global allocator and armed via set_hard_limit(memory_limit_with_headroom(...)); otherwise it is silently not enforced.

elapsed

pub fn elapsed(&self) -> Duration

Returns the cumulative execution time: bytecode-execution wall time accumulated across runs/feeds, excluding time suspended on the host or idle between feeds. Includes the in-progress window if the VM is currently executing.

Nothing is bounded by this — it is reported to the host for telemetry. The budgets are feed_elapsed and turn_elapsed.

feed_elapsed

pub fn feed_elapsed(&self) -> Duration

Returns the execution time consumed by the current feed; see elapsed, which reports the same clock over the whole session.

turn_elapsed

pub fn turn_elapsed(&self) -> Duration

Returns the execution time consumed by the current host turn; see elapsed, which reports the same clock over the whole session.

max_feed_duration

pub fn max_feed_duration(&self) -> Option<Duration>

Returns the configured per-feed execution time limit, if any.

max_turn_duration

pub fn max_turn_duration(&self) -> Option<Duration>

Returns the configured per-turn execution time limit, if any.

max_memory

pub fn max_memory(&self) -> Option<usize>

Returns the configured memory budget, if any. Hosts that bound a worker process from outside the interpreter size that bound from this.

max_suspensions

pub fn max_suspensions(&self) -> usize

Returns the host-enforced suspension budget (default DEFAULT_MAX_SUSPENSIONS; never unlimited).

max_total_sleep

pub fn max_total_sleep(&self) -> Option<Duration>

Cumulative sleep limit for the host to enforce, including after restore.

has_memory_time_limit

pub fn has_memory_time_limit(&self) -> bool

Returns whether the VM has a memory or time limit configured.

has_time_limit

pub fn has_time_limit(&self) -> bool

Returns whether either execution-time budget is configured.

Public so callers that pay for finer-grained clock polling only when a budget exists (fstring’s incremental large-result path) can ask.

set_max_feed_duration

pub fn set_max_feed_duration(&mut self, duration: Duration)

Sets the per-feed execution limit as a fresh budget from now, resetting the feed (and so the turn) clock.

This lets a host enforce a different (typically shorter) time limit for a resumed phase — e.g. allowing a long build phase, then giving repr() of the result only a few milliseconds. Time spent suspended in the host never counts toward the budget either way.

set_max_turn_duration

pub fn set_max_turn_duration(&mut self, duration: Duration)

Sets the per-turn execution limit as a fresh budget from now, resetting the turn clock.

check_allocation

pub fn check_allocation(&self, additional: usize) -> Result<(), ResourceError>

Checks whether one up-front allocation fits the memory budget.

Use this before reserving a buffer that could cross both the soft and hard allocator limits before execution reaches another checkpoint.

check_memory_time

pub fn check_memory_time(&self) -> Result<(), ResourceError>

Called periodically to check allocator-backed memory and time limits.

Returns Ok(()) while configured limits are respected, or the relevant resource error once either limit is exceeded.

Takes &self rather than &mut self because checking elapsed time is a read-only operation. This allows time checks in contexts that only have an immutable heap reference, such as py_repr_fmt.

check_time

pub fn check_time(&self) -> Result<(), ResourceError>

Called periodically to check both execution-time budgets.

Each clock is monotonic within its scope, so once a budget is exceeded every later call in that scope fails too. The feed budget is tested first, since it is the one a new turn cannot recover from.

LOOP_CHECK_INTERVAL

pub const LOOP_CHECK_INTERVAL: usize = 64;

Items processed between full checks in amortized per-item Rust loops (see check_time_every). A limit can be overshot by up to this many items’ work before the next check — an accepted trade for cheap loops; the process-level hard limits backstop pathological cases.

check_time_every

pub fn check_time_every(&self, i: usize) -> Result<(), ResourceError>

Amortized per-item time check for Rust-side loops: a full clock read once per LOOP_CHECK_INTERVAL calls, free otherwise. Key i on the loop’s index or a monotonically increasing counter. Fires at the end of each block (i % N == N-1) so loops shorter than the interval pay no clock read at all — the VM dispatch checkpoint covers cadence between short calls.

check_memory_time_every

pub fn check_memory_time_every(&self, i: usize) -> Result<(), ResourceError>

Amortized per-item memory + time check; the memory-probing sibling of check_time_every, for loops that allocate per item. Between full checks the allocator’s hard limit still bounds runaway growth.

check_growth

pub fn check_growth(
    &self,
    len: usize,
    capacity: usize,
    elem_size: usize,
) -> Result<(), ResourceError>

Preflights the reallocation that pushing one more element onto a dense buffer causes; a push that fits the existing capacity costs nothing.

A Vec charges its whole doubling in one allocation, so a push straddling the soft limit can land past the allocator’s fixed hard-limit headroom, killing the worker with no checkpoint in between at which to raise MemoryError. Only for the one-push shape: a bulk reservation needs ResourceTracker::check_allocation sized for the whole result, since preflighting less than the final buffer — one operand of a merge, say — leaves the same window open.

growth_bytes

pub fn growth_bytes(len: usize, capacity: usize, elem_size: usize) -> usize

The bytes check_growth would preflight, or zero if the push allocates nothing.

Split out for containers that grow two buffers on one insertion, such as a dict’s entry vector and index table: checking each increment alone passes both while their sum clears the headroom, so the caller sums them and passes the total to check_pending_allocation.

check_pending_allocation

pub fn check_pending_allocation(&self, additional: usize) -> Result<(), ResourceError>

check_allocation for preflights whose increment may be zero: a push that allocates nothing must not pay for the usage probe.

check_recursion_depth

pub fn check_recursion_depth(&self, current_depth: usize) -> Result<(), ResourceError>

Called before pushing a new call frame to check recursion depth.

Returns Ok(()) if within recursion limit, or Err(ResourceError::Recursion) if the limit would be exceeded. current_depth is the call stack depth before the new frame is pushed.

check_large_result

pub fn check_large_result(&self, estimated_bytes: usize) -> Result<(), ResourceError>

Called before operations that may produce large results (>100KB).

This allows pre-emptive rejection of operations like 2 ** 10_000_000 before the memory is actually allocated. The check only happens for estimated result sizes above LARGE_RESULT_THRESHOLD to avoid overhead on small operations.

gc_interval

pub fn gc_interval(&self) -> Option<usize>

Returns the configured garbage collection interval, in GC-tracked allocations.

The cycle collector runs at most once per gc_interval GC-tracked allocations, and additionally short-circuits when no cycle candidates are pending — so programs that never form cycles pay no collector cost regardless of their allocation rate. None tells the heap to use its built-in default scheduling threshold.

on_execution_start

pub fn on_execution_start(&self)

Called when the VM enters its execution loop from a host boundary (VM::run_external), starting one execution window.

Paired with on_execution_stop and never nested — VM-internal re-entry (task switches, host-initiated function evaluation) uses the raw run loop, so its time falls inside the enclosing window. The execution-time clock runs between the pair; it is not running while execution is suspended waiting on the host (external function calls) or between feeds.

on_execution_stop

pub fn on_execution_stop(&self)

Called when the VM leaves its execution loop — on completion, error, or suspension at an external call. See on_execution_start.

on_feed_start

pub fn on_feed_start(&self)

Called when the host begins a new feed, resetting the max_feed_duration budget (and, since a feed opens a turn, the max_turn_duration one).

A feed spans every turn its snippet takes, so this must fire only at the snippet’s first turn — not at the resumes that continue it.

on_turn_start

pub fn on_turn_start(&self)

Called when the host hands control back to the interpreter — at a feed or a resume — resetting the max_turn_duration budget.

Continuations the VM resolves without the host (an already-settled future, a task switch) stay inside the turn that started them.

sandbox_sleep

pub fn sandbox_sleep(&self, duration: Duration)

Performs a standard-execution sleep without charging max_feed_duration. Restarts the execution clock only if it was running before the wait. All interpreter waits use block_for for platform-specific blocking.

Implements: Debug, Default, Deserialize<'de>, Serialize.

TimeLimitScope

pub enum TimeLimitScope {
    /// `ResourceLimits::max_feed_duration` — reset at each feed.
    Feed,
    /// `ResourceLimits::max_turn_duration` — reset at each feed and each
    /// resume.
    Turn,
}

Which of the two nested execution-time budgets a ResourceError::Time refers to.

Both read the same clock and differ only in when they are reset, so a turn’s time is charged to its feed as well: Feed >= Turn always holds.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq.

memory_limit_with_headroom

pub fn memory_limit_with_headroom(
    max_memory: Option<usize>,
    type_check: bool,
) -> Option<usize>;

Converts an interpreter soft memory limit into a worker allocator budget.

The returned budget includes operational headroom but remains relative to the worker baseline, which monty-alloc adds when arming its hard ceiling.

ExtFunctionResult

pub enum ExtFunctionResult {
    /// Continues execution with the return value from the external function.
    Return(MontyObject),
    /// Continues execution with the exception raised by the external function.
    Error(MontyException),
    /// Pending future — the external function is a coroutine.
    ///
    /// The `u32` is the `call_id` from the `FunctionCall` that created this
    /// snapshot. It is used to track the pending future so it can be resolved
    /// later via `ResolveFutures::resume()`.
    Future(u32),
    /// The function was not found, should result in a `NameError` exception.
    NotFound(String),
}

Return value or exception from an external function.

Implements: Debug, From<MontyException>, From<MontyObject>.

NameLookupResult

pub enum NameLookupResult {
    /// The name resolves to this value.
    Value(MontyObject),
    /// The name is undefined — the VM raises `NameError` / `AttributeError`.
    Undefined,
    /// Resolving the name raised this exception on the host; the VM raises
    /// it where the lookup suspended, like a failed external call.
    Error(MontyException),
}

Result of a name lookup from the host.

When the VM encounters an unresolved name (or a lazy attribute on a host-backed object), the host provides one of these:

  • Value(obj): The name resolves to this value (a plain name is cached in its namespace slot; an attribute is re-consulted on every access).
  • Undefined: The name does not exist — NameError for a plain name, AttributeError for an attribute.
  • Error(exc): Resolving it raised on the host — the exception is raised inside the sandbox, so hasattr() / getattr() defaults do not apply.

Implements: Debug, From<MontyException>, From<MontyObject>, From<Option<MontyObject>>.

AssertMessageAnnotations

pub enum AssertMessageAnnotations {
    /// Disable introspection; bare asserts use CPython's empty message.
    Off,
    /// Retain at most this many UTF-8 bytes per operand before any `…` suffix.
    /// Non-zero because `0` encodes `Off` on the wire.
    MaxBytes(std::num::NonZeroU32),
}

Controls the pytest-style introspected assert failure messages of CompileOptions::assert_message_annotations.

The choice is baked in at compile time (whether the introspecting opcodes are emitted) but the truncation limit is applied at runtime, so it also travels with serialized sessions.

DEFAULT_MAX_BYTES

pub const DEFAULT_MAX_BYTES: NonZeroU32 = _;

Operand-repr truncation used by Default and From<bool>.

enabled

pub fn enabled(self) -> bool

Whether the compiler should emit introspecting assert opcodes.

max_bytes

pub fn max_bytes(self) -> u32

Returns the wire value: 0 when disabled, otherwise the UTF-8 byte cap.

from_max_bytes

pub fn from_max_bytes(value: u32) -> Self

Decodes the wire value: 0 is off and any other value is the byte cap.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, From<bool>, PartialEq, Serialize, StructuralPartialEq.

CompileOptions

pub struct CompileOptions {
    /// Give failed `assert` statements pytest-style introspected messages,
    /// deliberately diverging from CPython; see `limitations/assert.md`.
    /// On by default with a 120-byte operand-repr truncation.
    pub assert_message_annotations: AssertMessageAnnotations,
    /// Sources longer than this many bytes are scanned for parser nesting
    /// before ruff sees them (see `limitations/language.md`); `0` scans every
    /// source and `usize::MAX` none. Defaults to `SOURCE_SCAN_THRESHOLD`.
    pub source_scan_threshold: usize,
}

Options controlling how Monty behavior diverges from plain CPython.

Consumed when code is compiled: a MontyRun bakes the choices into the program at construction, while a MontyRepl stores them so every snippet fed to the session compiles the same way.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Serialize.

SOURCE_SCAN_THRESHOLD

pub const SOURCE_SCAN_THRESHOLD: usize = 4_096usize;

Byte length up to which a source is compiled without the pre-parse nesting scan; the default for CompileOptions::source_scan_threshold.

ruff grows its parser stack on demand, outside the sandbox allocator, at roughly 2 KiB per nesting level, and a source cannot nest deeper than it is long. 4 KiB (about 40 lines of 100 characters) caps that untracked growth at ~8 MiB while sparing ordinary programs the extra lexer pass.

TypeCheckState

pub struct TypeCheckState {
    /// User-provided stubs plus every snippet that has completed successfully.
    pub committed_stubs: String,
    /// The in-flight snippet; committed on success, discarded on error.
    pub pending_snippet: Option<String>,
    /// How diagnostics are rendered by whoever runs the type checker.
    pub config: TypeCheckingConfig,
}

Per-session type-check state: successfully committed snippets accumulate as stubs so later snippets can reference names defined by earlier ones.

Implements: Clone, Debug, Deserialize<'de>, Serialize.

TypeCheckingConfig

pub struct TypeCheckingConfig {
    /// Output format.
    pub format: TypeCheckingFormat,
    /// Whether to include ANSI colour escapes. Only `Full` and `Concise`
    /// render any colour; the machine-readable formats ignore it.
    pub color: bool,
}

How a type check renders whatever diagnostics it finds.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.

TypeCheckingFormat

pub enum TypeCheckingFormat {
    /// Human-readable diagnostics with a source snippet and carets.
    Full,
    /// One `path:line:col: severity[rule] message` line per diagnostic.
    Concise,
    /// Azure Pipelines logging commands.
    Azure,
    /// A JSON array of diagnostic objects.
    Json,
    /// One JSON diagnostic object per line.
    JsonLines,
    /// Reviewdog diagnostic JSON.
    Rdjson,
    /// Pylint-compatible output.
    Pylint,
    /// GitLab Code Quality report JSON.
    Gitlab,
    /// GitHub Actions workflow commands.
    Github,
}

How type-check diagnostics are rendered into text.

Mirrors ty’s DiagnosticFormat. Rendering happens wherever the type checker runs (inside the worker for pool sessions), because ty’s structured diagnostics borrow the salsa database and cannot cross a process boundary — so the format has to be chosen before the check, not after it.

Serialized into session dumps by variant name, so a rename needs #[serde(alias)] to keep older dumps loading (see DUMP_VERSION in monty).

from_name

pub fn from_name(name: &str) -> Result<Self, String>

Parses a format name, reporting the valid names on failure.

Bindings take the format as a string, so the error has to be good enough to show a user who guessed wrong.

names

pub fn names() -> String

Comma-separated list of the accepted format names.

Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Display, Eq, FromStr, PartialEq, Serialize, StructuralPartialEq, TryFrom<&str>, VariantNames.

MontyUuid

pub struct MontyUuid(/* private */);

A 16-byte UUID identifying a host or sandbox class/instance across the sandbox boundary.

from_bytes

pub const fn from_bytes(bytes: [u8; 16]) -> Self

Wraps raw bytes as-is; used when the bytes are already a valid uuid.

from_u128

pub const fn from_u128(v: u128) -> Self

Builds a deterministic id from an integer — for tests and fixtures that must be reproducible; never use for real identity.

from_random_bytes

pub const fn from_random_bytes(bytes: [u8; 16]) -> Self

Stamps the uuid4 version and RFC 4122 variant bits over caller-supplied random bytes, so any entropy source yields a well-formed uuid4.

as_bytes

pub const fn as_bytes(&self) -> &[u8; 16]

Returns the raw bytes (big-endian field order, per RFC 4122).

try_from_slice

pub fn try_from_slice(bytes: &[u8]) -> Option<Self>

Parses exactly 16 bytes; None for any other length. This is the wire-decode entry point, so it must never panic.

parse

pub fn parse(s: &str) -> Option<Self>

Parses the canonical hyphenated form (8-4-4-4-12 hex digits), any case; None on any deviation. Used by the JS surfaces, which carry uuids as strings.

Implements: Clone, Copy, Debug, Deserialize<'de>, Display, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, StructuralPartialEq.

normalize_virtual_path

pub fn normalize_virtual_path(path: &str) -> std::borrow::Cow<'_, str>;

Returns an absolute POSIX path with . removed and .. resolved, stopping at /. Relative inputs are rooted at /; this never reads the host cwd or follows symlinks. Already normalized paths are borrowed. Validate NUL bytes and length limits first: normalization can remove invalid components and does not provide filesystem confinement.

validate_cwd

pub fn validate_cwd(cwd: &str) -> Result<String, String>;

Checks a host-supplied working directory: absolute, POSIX, no NUL bytes.

Trailing slashes are dropped so os.getcwd() never reports /data/; the root itself stays /. . and .. are left for the interpreter, which normalizes the directory when it adopts it. The error is the message of the ValueError hosts raise for it.

use monty_types::validate_cwd;

assert_eq!(validate_cwd("/data/").unwrap(), "/data");
assert_eq!(validate_cwd("data").unwrap_err(), "cwd must be an absolute POSIX path: \"data\"");