feat(sandbox-modal): Modal sandbox provider worker (Python)#96
feat(sandbox-modal): Modal sandbox provider worker (Python)#96rohitg00 wants to merge 4 commits into
Conversation
Modal is gRPC-only — no public REST surface — so this Python worker imports the official 'modal' SDK as transport. The SDK is an implementation detail; callers see only sandbox::modal::* trigger ids matching the canonical sandbox ABI. Six functions registered (create, exec, stop, list, snapshot, expose_port). fs::read/write deferred for v0 (Modal's open() handle API doesn't map cleanly to channel-shaped FS ops). Async concurrency tracking via asyncio.Lock. S-code error mapping via map_modal_error which resolves Modal exception class names without forcing the modal package import path during tests. 6/6 pytest cases pass; ruff check + format clean. ModalClient bodies stubbed pending verified pass against modal.Sandbox.create().
…gistry
Replaces TODO stubs with real modal.Sandbox.create(...) / sb.exec(...) /
sb.terminate() / sb.snapshot_filesystem() / sb.tunnels()[port] calls,
verified via context7 against modal.com/docs/reference/modal.Sandbox.
Implementation notes:
- Lazy import: `modal` loads inside each method (asyncio.to_thread)
so the worker module imports cleanly without Modal credentials. Tests
mock ModalClient and never trigger the import.
- Handle registry: a sandbox_id (= modal.Sandbox.object_id) maps to
the live Sandbox handle in an asyncio.Lock-guarded dict. Modal's SDK
doesn't ship a server-side list-all primitive, so we list what the
worker has tracked locally; do_list reconciles to that length.
- Idempotent stop: an unknown sandbox_id, or a Modal NotFoundError,
is treated as already-gone. Matches the family-wide stop contract.
- exec returns {stdout, stderr, exit_code} pulled from the modal Process
object (`proc.stdout.read()`, `proc.wait()`).
- expose_port surfaces a clear S502 if the port wasn't declared via
`encrypted_ports=[port]` at create time — Modal requires up-front
declaration and there's no per-port reconfigure.
- snapshot returns the Modal Image object_id from
`sb.snapshot_filesystem()`.
Test `test_stop_rolls_through_stub_client` retired in favour of
`test_stop_is_idempotent_when_sandbox_unknown` which pins the new
semantics. 6/6 tests pass; ruff check + format clean. Live verification
deferred until MODAL_TOKEN_ID/_SECRET are available.
iii.register_function(id, async_handler) signature unchanged across the bump; pytest still 6/6, ruff clean. Lazy-import preserves no-creds boot.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (13)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Stops shadowing the bare sandbox::* namespace. Routes through the sandbox router worker (provider="modal"); direct invocation stays supported and stable. Handler logic, tests, S-code mapping unchanged.
|
Refactored to register only Validation: |
Member of the sandbox worker family. Read `sandbox-CONTEXT.md` (lands via #92) for family terminology and ABI contract.
What ships
`sandbox-modal/` — narrow Python iii worker wrapping Modal sandboxes. Modal is gRPC-only (no public REST), so the worker imports the official `modal` Python SDK as its transport. The SDK is implementation detail; callers see only the canonical `sandbox::modal::*` ABI.
Functions
Capabilities advertised: `["snapshot","expose_port"]`. No `fs` (Modal's FileIO API doesn't map cleanly to channel-based FS today).
Implementation notes
Auth
`MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET` — read by the `modal` package directly. Worker fails fast at first SDK call if missing.
Tests
6 pytest cases mocking `ModalClient` (allowlist rejection, in_flight rollback, exec validation, idempotent stop on unknown id, list envelope, asyncio loop sanity). `ruff check` + `ruff format` clean.
Live test status
Wired against documented signatures; not yet end-to-end verified (no Modal credentials during authoring).
Pin
`iii-sdk==0.11.6`.