[Unreleased]

0.4.0

New ExMonty Features

  • Buffered open() builtin and file handles. Python open(path, mode) (with context-manager with support, read/write, and a/w/r modes) now works against :read_write and :overlay mounts. File objects round-trip as a new {:file_handle, %{path, mode, position}} tagged value, and three new os calls — :open, :append_text, :append_bytes — are dispatchable through Sandbox os: handler maps. PseudoFS does not yet service :open (returns NotImplementedError); use a mount for file I/O.

  • ExMonty.Mount — host filesystem mounts. Map virtual paths in the sandbox to real host directories with :read_only, :read_write, or :overlay access modes. Path canonicalisation, boundary checks, and symlink-escape detection are always enforced.

    mounts =
      ExMonty.Mount.new!()
      |> ExMonty.Mount.add!("/data",    "/var/lib/myapp/data", :read_only)
      |> ExMonty.Mount.add!("/scratch", "/tmp/sandbox-scratch", :overlay)
    
    ExMonty.Sandbox.run(code, mounts: mounts)

    Composes with the existing :os option — mounts handle FS calls; the :os map handles non-FS fallbacks (:getenv, :datetime_now, etc.). Unmounted paths raise PermissionError (upstream's OsFunction::on_no_handler semantics) instead of ExMonty's previous generic :os_error.

    Mount state (overlay writes, write_bytes_used counter) is cumulative on the mount object across runs. Construct a fresh mount to discard. See proposals/MOUNT_TABLE.md for the design rationale.

  • datetime support. date, datetime, timedelta, and timezone Python values round-trip via new tagged tuples:

    # Output direction (Python → Elixir)
    ExMonty.eval("from datetime import date\ndate(2026, 5, 1)")
    # {:ok, {:date, %{year: 2026, month: 5, day: 1}}, ""}
    
    # Input direction (Elixir → Python)
    ExMonty.eval("x.year",
      inputs: %{"x" => {:date, %{year: 2026, month: 5, day: 1}}})
    # {:ok, 2026, ""}
    
    # date.today() / datetime.now() via host handlers
    ExMonty.Sandbox.run("from datetime import date\ndate.today()",
      os: %{date_today: fn _, _ -> {:ok, {:date, %{year: 2026, month: 5, day: 1}}} end})

    Encoded shapes:

    • {:date, %{year, month, day}}
    • {:datetime, %{year, month, day, hour, minute, second, microsecond, offset_seconds, tz_name}} (offset_seconds and tz_name are nil for naive datetimes)
    • {:timedelta, %{days, seconds, microseconds}}
    • {:timezone, %{offset_seconds, name}}
  • New OS handler atoms :date_today and :datetime_now surfaced through ExMonty.Sandbox for hosts to provide deterministic clocks.

Bug Fixes

  • Sandbox handler allowlist. ExMonty.Sandbox now accepts :date_today / :datetime_now keys in os: handler maps (previously they were silently dropped).

Upstream Python Features (monty v0.0.8 → v0.0.18)

These come from upstream and don't change the ExMonty API — but they affect what Python code you can run:

  • open() builtin (v0.0.18). with open(...) as f:, buffered read/write, file objects. The with statement works for built-in context managers like open(); user-defined __enter__/__exit__ classes are still unsupported (no class definitions). See "Buffered open()" above.
  • json module. import json; json.loads(...) / json.dumps(...).
  • datetime module. date, datetime, timedelta, timezone (see above).
  • Multi-module imports. import a, b, c in one statement.
  • Chain assignment. a = b = c = 1.
  • Nested subscript assignment. d[k][i] = v, matrix[i][j] = 0.
  • hasattr / setattr. Work on host-provided objects (dataclasses, modules). Class definitions in Python source are still not supported.
  • zip(..., strict=True). Raises ValueError on length mismatch.
  • str.expandtabs(tabsize).
  • Named single-kwarg calls. f(x=1) (single-kwarg edge case fixed).
  • INT_MAX_STR_DIGITS guard. int("1" * 5000) raises ValueError, matching CPython's quadratic-time DoS protection (default 4300 digits).
  • Bug fixes: i64::MIN negation panic, source-line >65535 parse panic, partial future-resolution panic in mixed gathers, GC interval ignored in tracker, GC reference release in cycles, empty-tuple singleton counted against memory limit.
  • More hardening (v0.0.18): trial-deletion cycle GC, duplicate-parameter and duplicate-coroutine panics, exception-stack corruption on raise, further integer-op panics, comprehension generator/AST depth limits, and JSON BigInt limits — all converted from panics to proper exceptions or bounded errors.

Not Yet Exposed to Elixir

  • Filesystem mounting (MountTable API) — upstream supports overlay / read-only / layered filesystems. ExMonty's PseudoFS does not yet wrap this.
  • Async in Rust — internal VM async support; no surface change for Elixir callers today.
  • MontyRepl / JsonMontyObject — Rust-only APIs.

Internal

  • monty pinned to v0.0.18 (commit 45a3b2d5). Update procedure now tracks tagged releases by default; see UPDATE_PROCEDURE.md.
  • Upstream OsFunctionOsFunctionCall refactor absorbed. The os-call surface now carries typed args inline; our NIF extracts the (positional, keyword) view via OsFunctionCall::to_args and routes mount calls through the new MountTable::handle_os_call(&call) signature. No Elixir-side API change beyond the new :open/:append_* os atoms.
  • PrintWriter::Collect renamed to PrintWriter::CollectString in monty upstream — internal NIF change only, no Elixir-side impact.

0.3.0

Breaking Changes

  • Removed external_functions option from compile/2 and Sandbox.run/2. External functions are now auto-detected at runtime via name lookup. The :external_functions option is no longer accepted.
  • ExMonty.Native.compile/4 is now compile/3 (removed external_fns parameter).

New Features

  • Name lookup progress tag. When Python code references an undefined name (without calling it), execution pauses with {:name_lookup, name, snapshot, output}. Resume with {:ok, {:function, name}} to provide a callable, {:ok, value} for a constant, or :undefined to raise NameError.
  • MontyObject::Function type. A new tagged tuple {:function, name} (or {:function, name, docstring}) can be returned from name lookups to provide callable function objects to the Python VM.
  • handle_name_lookup/1 callback in ExMonty.Sandbox behaviour (optional). Called when the sandbox encounters an undefined name. The sandbox auto-resolves names found in the :functions map.

Upstream Improvements (monty v0.0.8)

  • re module implementation (regex support).
  • Full Python math module (~50 functions).
  • PEP 448 generalised unpacking ([*a, *b], {**a, **b}).
  • Tuple comparison operators (<, >, <=, >=).
  • Dict view and set/frozenset operators.
  • str and bytes comparison operators.
  • Augmented assignment on subscript targets (x[i] += 1).
  • max() kwargs/default support.
  • Bug fixes: stack-depth tracking, loop iterator depth, variable shadowing panic, os.environ panic in JS bindings.

0.2.1

  • Fix dataclass round-trip: ExMonty.Dataclass structs returned from handlers are now decoded back to MontyObject::Dataclass, preserving field access (p.x), frozen semantics, and method calls.
  • Add field_names and type_id fields to ExMonty.Dataclass struct for full fidelity with monty's dataclass representation.
  • Fix float inf/nan encoding: float('inf'), float('-inf'), and float('nan') now encode as :infinity, :neg_infinity, and :nan atoms instead of crashing.
  • Float special atoms round-trip through inputs.

0.2.0

  • Update monty to 47427c0 (v0.0.7).
  • Support dataclass method calls (new :method_call tag in interactive mode).
  • Upstream: filter(), map(), getattr() builtins, dict(iterable), dataclass methods, asyncio.run(), user-defined function calling.
  • Upstream: MontyException and StackFrame now serializable.
  • Upstream: improved resource limit enforcement (time check rate-limiting, 4x pow safety multiplier, catchable RecursionError).
  • Upstream: bug fixes for string comparison, async stack overflow, i64::MIN division overflow.
  • PrintWriter refactored from trait to enum (internal change, no public API impact).

0.1.1

  • Update monty to v0.0.4 (86712b6) — includes fuzz testing, small-tuple optimizations, AST depth overflow fix, and id() memory leak fix.

0.1.0

  • Initial release.
  • ExMonty.eval/2, compile/2, run/3 for sandboxed Python execution.
  • Interactive pause/resume (start/3, resume/2, resume_futures/2) for external function calls and OS calls.
  • ExMonty.Sandbox high-level handler + ExMonty.PseudoFS in-memory filesystem.
  • Runner/snapshot serialization (dump/1, load_runner/1, dump_snapshot/1, load_snapshot/1).