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.
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.
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.
Consumes the fields into arguments for delivery to the host.
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.
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.
pub struct StringRepr<'a>(pub &'a str);
Formatter for a Python repr() string.
Implements: Debug, Display.
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,\xNNfor non-printable bytes
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.
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.
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.
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. -18000 →
datetime.timedelta(days=-1, seconds=68400). Used by the
datetime.timezone reprs of boundary values.
pub const MONTY_VERSION: &str = "1.0.0-beta.2";
The monty version this build was compiled as.
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”).
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.
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.
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.
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.
pub fn unicode(&self) -> Option<&UnicodeErrorData>
The unicode-error fields, if this is ExcData::Unicode.
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.
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.
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:
Exceptionis the base class for all standard exceptionsLookupErroris the base forKeyErrorandIndexErrorArithmeticErroris the base forZeroDivisionErrorandOverflowErrorRuntimeErroris the base forRecursionErrorandNotImplementedError
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.
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).
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.
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.
pub struct MontyException { /* private fields */ }
Public representation of a Monty exception.
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.
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.
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.
pub fn data(&self) -> &ExcData
The structured payload, ExcData::None for most exceptions.
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.
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).
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.
pub fn add_traceback(&mut self, traceback: impl IntoIterator<Item = StackFrame>)
Appends frames to this exception’s traceback.
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.
pub fn exc_type(&self) -> ExcType
The exception type raised.
pub fn message(&self) -> Option<&str>
Optional exception message explaining what went wrong.
Equivalent of python’s exc.args[0]
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]
pub fn traceback(&self) -> &[StackFrame]
Stack trace of the exception, first is the outermost frame shown first in the traceback
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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'.
pub fn is_binary(&self) -> bool
Whether the file is binary ('rb', 'wb', …) rather than text.
pub fn readable(&self) -> bool
Whether read() is allowed by this mode.
pub fn writable(&self) -> bool
Whether write() is allowed by this mode.
pub fn is_append(&self) -> bool
Whether writes should always append (a/a+).
pub fn truncate(&self) -> bool
Whether open() must truncate the file to empty immediately (w/w+).
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.
pub fn type_name(&self) -> &'static str
Returns the bare Python type name (type(f).__name__) for this mode.
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.
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.
pub struct StringRepr<'a>(pub &'a str);
Formatter for a Python repr() string.
Implements: Debug, Display.
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.
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,\xNNfor non-printable bytes
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.
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.
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.
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.
pub fn entries(&self) -> &[(PrintStream, String)]
The collected fragments, in the order the sandbox produced them.
pub fn into_entries(self) -> Vec<(PrintStream, String)>
Takes the collected fragments, leaving the buffer empty.
Implements: Debug, Default.
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.
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.
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.
Disabled— silently discards all output (useful for benchmarking or suppressing output).Stdout— writes to standard output (the default behavior).CollectString— accumulates output into a targetStringfor programmatic access. No stream labels are preserved; every fragment is appended in the order it was emitted. TheOption<usize>is an optional byte cap (None= unlimited); constructorscollect_string/ default Python collectors useDEFAULT_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 asCollectString.Callback— delegates to a user-providedPrintWriterCallbackimplementation.
pub fn collect_string(buf: &mut String) -> PrintWriter<'_>
Collect into buf with the default DEFAULT_MAX_PRINT_COLLECT_BYTES cap.
pub fn collect_streams(buf: &mut CollectedStreams) -> PrintWriter<'_>
Collect into buf with the default DEFAULT_MAX_PRINT_COLLECT_BYTES cap.
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.
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.
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.
pub fn stdout_write(&mut self, output: Cow<'_, str>) -> Result<(), MontyException>
pub fn stdout_push(&mut self, end: char) -> Result<(), MontyException>
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.
pub fn poll_flush(&mut self) -> Result<(), MontyException>
Forwards the VM’s periodic checkpoint to a buffering callback.
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.
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.
output- The formatted output string for a single argument (without separators or trailing newline).
Add a single character to stdout.
Generally called to add spaces and newlines within print output.
end- The character to print after the formatted output.
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.
Adds a single character to stderr. Defaults to
stdout_push, as stderr_write does.
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.
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.
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.
pub fn new() -> Self
No arguments.
pub fn push_arg(&mut self, value: MontyObject)
Appends a positional argument.
pub fn push_kwarg(&mut self, name: &str, value: MontyObject)
Appends a keyword argument with a string key.
pub fn arg(&self, index: usize) -> Option<ObjectRef<'_>>
The indexth positional argument.
pub fn args(&self) -> impl ExactSizeIterator<Item = ObjectRef<'_>>
The positional arguments, in order.
pub fn kwargs(&self) -> impl ExactSizeIterator<Item = (ObjectRef<'_>, ObjectRef<'_>)>
The keyword arguments as (key, value) views, in order.
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.
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.
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.
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
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>.
pub const MAX_TIMEZONE_OFFSET_SECONDS: i32 = 86_399;
Largest UTC offset datetime.timezone accepts, +23:59:59.
See 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.
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.
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.
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.
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.
pub fn none() -> Self
Python None.
pub fn ellipsis() -> Self
Python Ellipsis.
pub fn not_implemented() -> Self
Python NotImplemented.
pub fn bool(value: bool) -> Self
A bool.
pub fn int(value: i64) -> Self
An int that fits in 64 bits.
pub fn bigint(value: BigInt) -> Self
An int of any size.
pub fn float(value: f64) -> Self
A float.
pub fn string(value: impl Into<String>) -> Self
A str.
pub fn bytes(value: impl Into<Vec<u8>>) -> Self
A bytes.
pub fn path(value: impl Into<String>) -> Self
A pathlib.Path, always a virtual POSIX path.
pub fn date(value: MontyDate) -> Self
A datetime.date.
pub fn datetime(value: MontyDateTime) -> Self
A datetime.datetime.
pub fn time(value: MontyTime) -> Self
A datetime.time.
pub fn timedelta(value: MontyTimeDelta) -> Self
A datetime.timedelta.
pub fn timezone(value: MontyTimeZone) -> Self
A datetime.timezone.
pub fn exception(exc_type: ExcType, arg: Option<String>) -> Self
An exception instance as a value (not raised), with its message.
pub fn function(name: impl Into<String>, docstring: Option<String>) -> Self
A host function the sandbox calls back by name.
pub fn builtin_function(function: BuiltinsFunctions) -> Self
A builtin function such as len.
pub fn type_object(value: MontyType) -> Self
A builtin type object such as int.
pub fn file_handle(value: MontyFileHandle) -> Self
An open file object, as the result of an open() OS call.
pub fn repr(value: impl Into<String>) -> Self
Output-only: a value’s repr() where no faithful representation exists.
pub fn cycle(placeholder: impl Into<String>) -> Self
Output-only: a reference back to an enclosing container, as its placeholder.
pub fn list(items: impl IntoIterator<Item = Self>) -> Self
A list.
pub fn tuple(items: impl IntoIterator<Item = Self>) -> Self
A tuple.
pub fn set(items: impl IntoIterator<Item = Self>) -> Self
A set.
pub fn frozenset(items: impl IntoIterator<Item = Self>) -> Self
A frozenset.
pub fn dict(pairs: impl IntoIterator<Item = (Self, Self)>) -> Self
A dict from (key, value) pairs, in insertion order.
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, ...).
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.
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.
If class_type is not a class type object.
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.
pub fn as_ref(&self) -> ObjectRef<'_>
Borrows the value for inspection without copying.
pub fn py_repr(&self) -> String
The Python repr() of the value.
pub fn is_truthy(&self) -> bool
Whether the value is truthy under Python’s rules; see ObjectRef::is_truthy.
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>.
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.
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.
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.
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.
pub fn name(&self) -> &str
The Python-visible name of this type ("int", "datetime.datetime",
"ValueError").
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.
pub struct NamedValues { /* private fields */ }
The named inputs of one feed, preserving sharing between exported values.
pub fn new() -> Self
No values.
pub fn push(&mut self, name: impl Into<String>, value: MontyObject)
Appends a named value.
pub fn len(&self) -> usize
Number of named values.
pub fn is_empty(&self) -> bool
Whether there are no named values.
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.
pub struct ObjectRef<'a> { /* private fields */ }
A borrowed Python value, for inspecting results, arguments and inputs without copying.
pub fn type_name(&self) -> &'a str
The Python type name of the value, e.g. "list".
pub fn to_owned(&self) -> MontyObject
Copies into an owned value, preserving sharing within the value.
pub fn items(&self) -> Option<Vec<Self>>
The items of a list, tuple, set, frozenset or namedtuple; None for
any other value.
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.
pub fn as_int(&self) -> Option<i64>
The value as an int, if it fits in 64 bits.
pub fn as_str(&self) -> Option<&'a str>
The value as a str.
pub fn as_bool(&self) -> Option<bool>
The value as a bool.
pub fn as_float(&self) -> Option<f64>
The value as a float; an int converts as Python’s float() does.
pub fn py_repr(&self) -> String
The Python repr() of the value.
Could panic if out of memory.
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<'_>>.
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.
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.
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.
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.
pub fn new() -> Self
An empty arena.
pub fn with_capacity(capacity: usize) -> Self
An empty arena with room for capacity nodes.
pub fn from_nodes(nodes: Vec<MontyNode>) -> Result<Self, GraphError>
Adopts already-built nodes after validating them.
pub fn validate(nodes: &[MontyNode]) -> Result<(), GraphError>
Checks that child ids precede their holders and class-instance nodes reference class-type nodes.
pub fn push(&mut self, node: MontyNode) -> NodeId
Appends a node, returning its id.
If the node violates the arena invariants; children must be pushed before the node that holds them.
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.
pub fn len(&self) -> usize
Number of nodes.
pub fn is_empty(&self) -> bool
Whether the arena has no nodes.
pub fn node(&self, id: NodeId) -> &MontyNode
The node at id.
If id is out of range; ids come from this arena, so that is a bug.
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.
If id is out of range.
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.
pub fn nodes(&self) -> &[MontyNode]
Every node, in post-order.
pub fn into_nodes(self) -> Vec<MontyNode>
Takes the nodes out of the arena.
pub fn check_root(&self, root: NodeId) -> Result<(), GraphError>
Checks that root (an id received alongside this arena) is in range.
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.
pub fn decoded_size(&self) -> usize
Sum of MontyNode::decoded_size over every node: the arena’s decoded
footprint, used by transport budgets.
pub fn value(&self, id: NodeId) -> ObjectRef<'_>
Borrows the value rooted at id.
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.
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.
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.
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.
pub fn cycle_placeholder(&self) -> &'static str
The placeholder a reference back to this node renders as, matching
CPython’s recursive repr() markers.
pub fn is_leaf(&self) -> bool
Whether the node holds no child ids.
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.
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.
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.
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.
pub type CallArgsParts = (MontyGraph, Vec<NodeId>, Vec<(NodeId, NodeId)>);
The owned arena, positional roots and keyword roots of a call.
pub type CallArgsPartsRef<'a> = (&'a MontyGraph, &'a [NodeId], &'a [(NodeId, NodeId)]);
Borrowed call storage, without copying nodes or roots.
pub type CallArgsPartsMut<'a> = (&'a mut MontyGraph, &'a mut Vec<NodeId>, &'a mut Vec<(NodeId, NodeId)>);
Mutable call storage for graph-level construction.
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());
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.
pub fn object_from_graph(
graph: MontyGraph,
root: NodeId,
) -> Result<MontyObject, GraphError>;
Pairs an arena with a root, checking the root is in range.
pub fn object_from_node(node: MontyNode) -> MontyObject;
Builds a value from a single node.
If the node holds child ids or otherwise fails graph validation.
pub fn root_node(value: &MontyObject) -> &MontyNode;
Borrows the stored root node, exposing its arena-relative child ids.
pub fn node(value: ObjectRef<'_>) -> &MontyNode;
Borrows the stored node of a value view, exposing its arena-relative child ids.
pub fn child(value: ObjectRef<'_>, id: NodeId) -> ObjectRef<'_>;
Borrows another node from the view’s arena.
If id is out of range; ids must come from this arena.
pub fn call_args_parts(args: &CallArgs) -> CallArgsPartsRef<'_>;
Borrows the arena and roots of a call without copying.
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.
pub fn into_call_args_parts(args: CallArgs) -> CallArgsParts;
Takes the arena and roots of a call without copying.
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.
pub fn named_values_parts(values: &NamedValues) -> (&MontyGraph, &[(String, NodeId)]);
Borrows the arena and named roots of a feed without copying.
pub fn into_named_values_parts(values: NamedValues) -> (MontyGraph, Vec<(String, NodeId)>);
Takes the arena and named roots of a feed without copying.
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.
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.
pub fn push_kwarg(args: &mut CallArgs, name: &str, value: impl PushValue);
Appends a keyword argument directly to the call’s arena.
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.
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.
Appends a value with its children below it, returning its arena-relative id.
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.
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.
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.
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.
pub fn new(path: String) -> Self
Stores the path without validation or normalization; hosts validate before I/O.
pub fn as_str(&self) -> &str
Borrows the original spelling for host validation and error messages.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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).
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.
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.
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.
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.
pub struct RenameCallArgs {
pub src: MontyPath,
pub dst: MontyPath,
}
rename(src, dst) shape.
Implements: Clone, Debug, Deserialize<'de>, Serialize, ToArgs.
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.
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).
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>.
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.
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.
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
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.
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 bytesmtime- Modification time as Unix timestamp
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.
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.
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.
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
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.
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.
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.
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>.
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.
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.
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.
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.
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.
pub fn utc() -> Self
UTC named UTC, as CPython reports the zone under TZ=UTC.
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.
pub fn iana_name(&self) -> Option<&str>
The IANA name of a Named zone; None for a fixed offset.
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.
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.
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.
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.
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.
pub const DEFAULT_MAX: Duration = _;
Default per-call cap for System.
Implements: Clone, Copy, Debug, Default, Deserialize<'de>, Eq, PartialEq, Serialize, StructuralPartialEq.
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.
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.
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.
pub fn unix_seconds(utc: chrono::NaiveDateTime) -> f64;
The instant as time.time() reports it: seconds since the Unix epoch.
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.
pub const DEFAULT_MAX_RECURSION_DEPTH: usize = 1000;
Recommended maximum recursion depth if not otherwise specified.
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.
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.
pub static LIVE_MEMORY: std::sync::atomic::AtomicUsize;
Allocator-backed live bytes requested through the global allocator
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.
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>.
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.
pub fn max_feed_duration(self, limit: Duration) -> Self
Sets the maximum execution duration for any single feed.
pub fn max_turn_duration(self, limit: Duration) -> Self
Sets the maximum execution duration for any single host turn.
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.
pub fn gc_interval(self, interval: usize) -> Self
Sets the garbage collection interval (run GC every N GC-tracked allocations).
pub fn max_recursion_depth(self, limit: usize) -> Self
Sets the maximum recursion depth (function call stack depth).
pub fn max_suspensions(self, limit: usize) -> Self
Sets the host-enforced maximum number of suspensions.
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.
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.
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.
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.
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.
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.
pub fn max_feed_duration(&self) -> Option<Duration>
Returns the configured per-feed execution time limit, if any.
pub fn max_turn_duration(&self) -> Option<Duration>
Returns the configured per-turn execution time limit, if any.
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.
pub fn max_suspensions(&self) -> usize
Returns the host-enforced suspension budget (default
DEFAULT_MAX_SUSPENSIONS; never unlimited).
pub fn max_total_sleep(&self) -> Option<Duration>
Cumulative sleep limit for the host to enforce, including after restore.
pub fn has_memory_time_limit(&self) -> bool
Returns whether the VM has a memory or time limit configured.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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>.
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 —NameErrorfor a plain name,AttributeErrorfor an attribute.Error(exc): Resolving it raised on the host — the exception is raised inside the sandbox, sohasattr()/getattr()defaults do not apply.
Implements: Debug, From<MontyException>, From<MontyObject>, From<Option<MontyObject>>.
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.
pub const DEFAULT_MAX_BYTES: NonZeroU32 = _;
Operand-repr truncation used by Default and From<bool>.
pub fn enabled(self) -> bool
Whether the compiler should emit introspecting assert opcodes.
pub fn max_bytes(self) -> u32
Returns the wire value: 0 when disabled, otherwise the UTF-8 byte cap.
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.
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.
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.
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.
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.
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).
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.
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.
pub struct MontyUuid(/* private */);
A 16-byte UUID identifying a host or sandbox class/instance across the sandbox boundary.
pub const fn from_bytes(bytes: [u8; 16]) -> Self
Wraps raw bytes as-is; used when the bytes are already a valid uuid.
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.
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.
pub const fn as_bytes(&self) -> &[u8; 16]
Returns the raw bytes (big-endian field order, per RFC 4122).
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.
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.
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.
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\"");