feat(code_runner): rename module, fix __del__ + kernel name, expand tests

- Rename `wrenn.code_interpreter` → `wrenn.code_runner` (canonical).
  Keep old path as deprecation alias that emits a FutureWarning on
  import, mirroring the existing `Sandbox` → `Capsule` pattern.
  Submodule shims `code_interpreter/{capsule,async_capsule,models}.py`
  keep direct-submodule imports working.

- Fix sync/async ctor-failure-safe `__del__`: initialise `_kernel_id`,
  `_kernel_name`, `_proxy_client` before calling `super().__init__` so
  a failed creation no longer crashes the destructor with
  AttributeError.

- Send the kernel name to Jupyter. Previously `POST /api/kernels` had
  no body, so the server picked an arbitrary default kernelspec. Now
  sends `{"name": "wrenn"}` (override via `Capsule(kernel=...)`) and
  reuses an existing kernel only when its `name` matches.

- Preserve Jupyter `text/plain` verbatim in `Result.from_bundle`.
  The previous outer-quote strip was lossy (the string `'2'` became
  indistinguishable from the int `2`, and strings containing escaped
  quotes were mangled). `text` is now the `repr()` Jupyter sends.
  Updated the stale `test_capsule_features` quote-strip test.

- Validate `run_code(language=...)`. Anything other than `"python"`
  now raises `ValueError` instead of being silently ignored.

- Async `__del__` no longer touches the event loop; users must call
  `await close()` or use `async with`.

- New unit suite `tests/test_code_runner_unit.py` (46 tests): MIME
  unpacking, deprecation alias + warning, default template + kernel,
  custom kernel override, ctor-failure-safe __del__, kernel
  create/reuse/cache, retry on 5xx, 4xx propagation, request shape,
  run_code stream/result/error/foreign-parent/idle/unsupported-language,
  async variants.

- New e2e suite `tests/test_code_runner_e2e.py` (44 tests, integration
  marker): template == `code-runner-beta`, kernel == `wrenn`, stdout
  /stderr capture, state/import/function/class persistence, exceptions
  (Value/Name/Syntax), callbacks, multi-line, `text` repr preservation,
  filesystem round-trip, isolation between capsules, deprecated import
  path. MIME-type class covers html, markdown, json, latex, svg,
  javascript, png (matplotlib + seaborn), jpeg, multi-format bundles,
  and text-round-trip via numpy + requests.

- `make test-code-runner` runs unit + e2e together. `make test`
  extended to include the unit file.

- README: "Code Interpreter" section renamed to "Code Runner", all
  imports updated, `kernel=` documented, removed the incorrect
  "quotes stripped automatically" claim, replaced with the actual
  `text/plain` semantics.

- CLAUDE.md: appended a "Code Runner Module" section covering module
  path, defaults, kernel-reuse semantics, lifecycle invariant, and
  the new test files + make target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 04:29:31 +06:00
parent 369c75af24
commit 9edde7bff5
14 changed files with 2116 additions and 776 deletions

View File

@ -84,10 +84,10 @@ capsule = Capsule.connect("cl-abc123")
result = capsule.commands.run("echo still running")
```
For code interpreter capsules:
For code runner capsules:
```python
from wrenn.code_interpreter import Capsule as CodeCapsule
from wrenn.code_runner import Capsule as CodeCapsule
capsule = CodeCapsule.connect("cl-abc123")
result = capsule.run_code("print('reconnected')")
@ -329,14 +329,16 @@ template = capsule.create_snapshot(name="my-template", overwrite=True)
---
## Code Interpreter
## Code Runner
The `wrenn.code_interpreter` module provides a specialized capsule for stateful code execution via a persistent Jupyter kernel.
The `wrenn.code_runner` module provides a specialized capsule for stateful code execution via a persistent Jupyter kernel. Defaults to the `code-runner-beta` template and the `wrenn` Jupyter kernelspec.
> The legacy module path `wrenn.code_interpreter` still works but emits a `FutureWarning` on import. Use `wrenn.code_runner`.
### Quick Start
```python
from wrenn.code_interpreter import Capsule
from wrenn.code_runner import Capsule
with Capsule(wait=True) as capsule:
result = capsule.run_code("print('hello')")
@ -348,7 +350,7 @@ with Capsule(wait=True) as capsule:
Variables, imports, and function definitions persist across `run_code` calls:
```python
from wrenn.code_interpreter import Capsule
from wrenn.code_runner import Capsule
with Capsule(wait=True) as capsule:
capsule.run_code("x = 42")
@ -403,15 +405,21 @@ capsule.run_code(
)
```
### Custom Templates
### Custom Templates and Kernels
By default, `code-runner-beta` template is used. You can specify a custom template:
By default, the `code-runner-beta` template and the `wrenn` Jupyter kernelspec are used. Override either:
```python
capsule = Capsule(template="my-custom-jupyter-template", wait=True)
capsule = Capsule(
template="my-custom-jupyter-template",
kernel="python3",
wait=True,
)
result = capsule.run_code("print('running on custom template')")
```
`Capsule` reuses the first kernel matching the requested `kernel` name on the Jupyter server and creates one if none exists.
### Execution Model
`run_code()` returns an `Execution` object:
@ -424,14 +432,14 @@ result = capsule.run_code("print('running on custom template')")
| `execution_count` | `int \| None` | Jupyter cell execution counter |
| `text` | `str \| None` | (property) `text/plain` of the main `execute_result` |
Each `Result` has typed MIME fields: `text`, `html`, `markdown`, `svg`, `png`, `jpeg`, `pdf`, `latex`, `json`, `javascript`, plus `extra` for unknown types. String expression results have quotes stripped automatically.
Each `Result` has typed MIME fields: `text`, `html`, `markdown`, `svg`, `png`, `jpeg`, `pdf`, `latex`, `json`, `javascript`, plus `extra` for unknown types. The `text` field is Jupyter's `text/plain` bundle verbatim — the Python `repr()` of the cell's last expression. So `run_code("'hi'").text` is `"'hi'"` (with quotes), and `run_code("42").text` is `"42"`. This preserves the distinction between the string `'2'` and the int `2`.
### Code Interpreter + Commands/Files
### Code Runner + Commands/Files
The code interpreter capsule inherits all standard capsule features:
The code runner capsule inherits all standard capsule features:
```python
from wrenn.code_interpreter import Capsule
from wrenn.code_runner import Capsule
with Capsule(wait=True) as capsule:
# Use run_code for Jupyter execution
@ -469,10 +477,10 @@ async with await AsyncCapsule.create(template="minimal", wait=True) as capsule:
await capsule.resume()
```
### Async Code Interpreter
### Async Code Runner
```python
from wrenn.code_interpreter import AsyncCapsule
from wrenn.code_runner import AsyncCapsule
async with await AsyncCapsule.create(wait=True) as capsule:
result = await capsule.run_code("2 + 2")